@fedify/fedify 2.3.0-dev.994 → 2.3.0-pr.809.36
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +16 -7
- package/dist/{assert-DikXweDx.mjs → assert-OguE97r2.mjs} +1 -1
- package/dist/{assert_instance_of-C4Ri6VuN.mjs → assert_instance_of-DBC5X09g.mjs} +1 -1
- package/dist/{assert_not_equals--wG9hV7u.mjs → assert_not_equals-DkVK8oqV.mjs} +1 -1
- package/dist/{assert_rejects-B-qJtC9Z.mjs → assert_rejects-DN60FHPX.mjs} +28 -3
- package/dist/{assert_strict_equals-Dmjbg-bA.mjs → assert_strict_equals-XEgZAlrj.mjs} +1 -1
- package/dist/{assert_throws-4NwKEy2q.mjs → assert_throws-BOkhLGYc.mjs} +1 -1
- package/dist/{builder-yAlpiIqP.mjs → builder-DSZ7FZmg.mjs} +96 -42
- package/dist/{chunk-nlSIicah.js → chunk-CRNNMoPX.js} +2 -2
- package/dist/circuit-breaker-CSWsyoef.mjs +337 -0
- package/dist/{client-z-8dc-e1.d.cts → client-CAM_bQXx.d.cts} +1 -0
- package/dist/{client-AtlibPOU.d.ts → client-CSddvgWN.d.ts} +1 -2
- package/dist/compat/mod.d.cts +2 -1
- package/dist/compat/mod.d.ts +2 -3
- package/dist/compat/mod.js +3 -3
- package/dist/compat/outgoing-jsonld.test.mjs +4 -4
- package/dist/compat/public-audience.test.mjs +4 -4
- package/dist/compat/transformers.test.mjs +5 -5
- package/dist/{context-BzH2-ajs.d.ts → context-BBVLF7lx.d.cts} +342 -269
- package/dist/{context-DJGagtNd.d.cts → context-BU6jSQdo.d.ts} +344 -266
- package/dist/{context-Dk_tacqz.mjs → context-DVoTs_wM.mjs} +64 -5
- package/dist/{deno-XBVlKnOX.mjs → deno-DCdM7d93.mjs} +1 -1
- package/dist/{docloader-DPp-X6gk.mjs → docloader-B7mVwA5_.mjs} +4 -4
- package/dist/{esm-DVILvP5e.mjs → esm-vrlUxr60.mjs} +4 -7
- package/dist/federation/builder.test.mjs +163 -11
- package/dist/federation/circuit-breaker.test.d.mts +2 -0
- package/dist/federation/circuit-breaker.test.mjs +446 -0
- package/dist/federation/collection.test.mjs +3 -3
- package/dist/federation/handler.test.mjs +1770 -29
- package/dist/federation/idempotency.test.mjs +5 -5
- package/dist/federation/inbox.test.mjs +4 -4
- package/dist/federation/keycache.test.mjs +5 -5
- package/dist/federation/kv.test.mjs +2 -2
- package/dist/federation/metrics.test.d.mts +2 -0
- package/dist/federation/metrics.test.mjs +634 -0
- package/dist/federation/middleware.test.mjs +4036 -146
- package/dist/federation/mod.cjs +195 -14
- package/dist/federation/mod.d.cts +6 -4
- package/dist/federation/mod.d.ts +6 -6
- package/dist/federation/mod.js +192 -14
- package/dist/federation/mq.test.mjs +176 -15
- package/dist/federation/negotiation.test.mjs +4 -4
- package/dist/federation/retry.test.mjs +3 -3
- package/dist/federation/router.test.mjs +190 -9
- package/dist/federation/send.test.mjs +153 -15
- package/dist/federation/temporal.test.d.mts +2 -0
- package/dist/federation/temporal.test.mjs +71 -0
- package/dist/federation/webfinger.test.mjs +150 -5
- package/dist/{http-CSlLA54v.mjs → http-BufbkLuW.mjs} +146 -47
- package/dist/{http-B4rrzxtg.js → http-Dh18nwJg.js} +994 -64
- package/dist/{http-aQzN9Ayi.d.ts → http-VyDTd4G3.d.cts} +15 -3
- package/dist/{http-BrGqJDXL.cjs → http-gOiudB3g.cjs} +1099 -61
- package/dist/{http-CrGuipxe.d.cts → http-lf8Hsd91.d.ts} +15 -1
- package/dist/{key-Bzx--sHD.mjs → key-DZdKLJXT.mjs} +43 -18
- package/dist/{kv-CbLNp3zQ.d.cts → kv-D6hNiMTK.d.ts} +1 -0
- package/dist/{kv-cache-DI-Sk7-I.cjs → kv-cache-BuK_nlpY.cjs} +20 -3
- package/dist/{kv-cache-U__xU4qR.mjs → kv-cache-C7SQnkGI.mjs} +21 -3
- package/dist/{kv-cache-EMIqoIuB.js → kv-cache-DNDSb6Po.js} +20 -3
- package/dist/{kv-GFYnFoOl.d.ts → kv-gJ8LYbxX.d.cts} +1 -3
- package/dist/ld-0Lsznacf.mjs +626 -0
- package/dist/metrics-BHeWd24f.mjs +815 -0
- package/dist/{middleware-zjZ6AFCW.mjs → middleware-BNANvDh2.mjs} +1 -1
- package/dist/{middleware-nidH7VZH.js → middleware-BgzRkDcm.js} +2260 -556
- package/dist/{middleware-An4DjES4.cjs → middleware-D-iES1nQ.cjs} +2318 -623
- package/dist/{middleware-K1g6bEkV.mjs → middleware-EV6J_eAc.mjs} +1639 -373
- package/dist/{mod-CR8soWa9.d.ts → mod-B0hW12_O.d.cts} +26 -4
- package/dist/mod-C0F6kvgS.d.cts +182 -0
- package/dist/{mod-Cr3f-ACa.d.cts → mod-COIAjwRS.d.ts} +26 -2
- package/dist/{mod-CMEbIaNh.d.cts → mod-DFvNJcNb.d.ts} +56 -4
- package/dist/mod-vPYVoa5n.d.ts +182 -0
- package/dist/{mod-CLgIXe9w.d.ts → mod-yvIXFAEi.d.cts} +56 -6
- package/dist/mod.cjs +9 -6
- package/dist/mod.d.cts +12 -10
- package/dist/mod.d.ts +12 -12
- package/dist/mod.js +11 -11
- package/dist/mq-D-nlpY04.d.ts +208 -0
- package/dist/mq-D8uSFzxe.d.cts +208 -0
- package/dist/{negotiation-SQvQgUqe.mjs → negotiation-DDstyBvc.mjs} +29 -0
- package/dist/nodeinfo/client.test.mjs +4 -4
- package/dist/nodeinfo/handler.test.mjs +4 -4
- package/dist/nodeinfo/mod.d.cts +2 -1
- package/dist/nodeinfo/mod.d.ts +2 -3
- package/dist/nodeinfo/mod.js +3 -3
- package/dist/nodeinfo/types.test.mjs +3 -3
- package/dist/otel/exporter.test.mjs +28 -25
- package/dist/otel/mod.cjs +6 -5
- package/dist/otel/mod.d.cts +5 -3
- package/dist/otel/mod.d.ts +5 -5
- package/dist/otel/mod.js +8 -7
- package/dist/{outgoing-jsonld-CNmZLixq.mjs → outgoing-jsonld-L_DbOaFe.mjs} +2 -2
- package/dist/{owner-C-tqfvxn.mjs → owner-Cl-1cAR8.mjs} +2 -2
- package/dist/{owner-CptqhsOy.d.cts → owner-CnngXDNJ.d.ts} +2 -1
- package/dist/{owner-74ARJ5TL.d.ts → owner-DEvZuyOE.d.cts} +2 -3
- package/dist/{proof-Bbbwf8_x.js → proof-CyXndf6-.js} +378 -15
- package/dist/{proof-BlmRPzrK.mjs → proof-Dnz8jtiQ.mjs} +26 -8
- package/dist/{proof-BW89QMVB.cjs → proof-tz35unOj.cjs} +432 -15
- package/dist/runtime/mod.d.cts +1 -0
- package/dist/runtime/mod.d.ts +1 -2
- package/dist/runtime/mod.js +3 -3
- package/dist/{send-05pJLcb6.mjs → send-DIbBYN5P.mjs} +72 -26
- package/dist/sig/http.test.mjs +356 -33
- package/dist/sig/key.test.mjs +103 -6
- package/dist/sig/ld.test.mjs +696 -7
- package/dist/sig/mod.cjs +2 -2
- package/dist/sig/mod.d.cts +4 -3
- package/dist/sig/mod.d.ts +4 -5
- package/dist/sig/mod.js +4 -4
- package/dist/sig/owner.test.mjs +6 -6
- package/dist/sig/proof.test.mjs +169 -8
- package/dist/{std__assert-CRDpx_HF.mjs → std__assert-BBjXFNOb.mjs} +5 -30
- package/dist/temporal-BEwob1Vg.mjs +95 -0
- package/dist/testing/mod.d.mts +110 -10
- package/dist/testing/mod.mjs +1 -2
- package/dist/{transformers-ve6e2xcg.js → transformers-BGMIq1cs.js} +2 -2
- package/dist/{types-hvL8ElAs.js → types-CAY3OdLq.js} +2 -2
- package/dist/utils/docloader.test.mjs +6 -6
- package/dist/utils/kv-cache.test.mjs +67 -2
- package/dist/utils/mod.cjs +1 -1
- package/dist/utils/mod.d.cts +2 -1
- package/dist/utils/mod.d.ts +2 -3
- package/dist/utils/mod.js +3 -3
- package/dist/vocab/cjs.test.mjs +1 -1
- package/dist/vocab/mod.d.cts +1 -0
- package/dist/vocab/mod.d.ts +1 -2
- package/dist/vocab/mod.js +2 -2
- package/package.json +16 -16
- package/dist/ld-DR78beiv.mjs +0 -279
- package/dist/middleware-BFBaR0mF.cjs +0 -4
- package/dist/mod-2d12ffz3.d.ts +0 -64
- package/dist/mod-D35TRn09.d.cts +0 -62
- package/dist/router-CrMLXoOr.mjs +0 -114
- package/dist/{activity-listener-ell7W1s9.mjs → activity-listener-tztVvlNb.mjs} +0 -0
- package/dist/{assert_equals-Ew3jOFa3.mjs → assert_equals-C-ZRDbaf.mjs} +0 -0
- package/dist/{client-D_1QpnWt.mjs → client-ByXmQhYD.mjs} +1 -1
- package/dist/{collection-D-HqUuA2.mjs → collection-Cc3DVAhE.mjs} +0 -0
- package/dist/{keycache-EGATflN-.mjs → keycache-BeU0LCII.mjs} +0 -0
- package/dist/{public-audience-DYFHzm_c.mjs → public-audience-Cvbr2Gzt.mjs} +1 -1
- /package/dist/{retry-bMXBL97A.mjs → retry-CXg_MBI-.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-
|
|
14
|
+
var version = "2.3.0-pr.809.36+c592d116";
|
|
15
15
|
//#endregion
|
|
16
16
|
//#region src/sig/accept.ts
|
|
17
17
|
/**
|
|
@@ -152,6 +152,814 @@ 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
|
+
queueDepth;
|
|
173
|
+
fanoutRecipients;
|
|
174
|
+
inboxActivity;
|
|
175
|
+
outboxActivity;
|
|
176
|
+
circuitBreakerStateChange;
|
|
177
|
+
keyLookup;
|
|
178
|
+
keyLookupDuration;
|
|
179
|
+
documentFetch;
|
|
180
|
+
documentFetchDuration;
|
|
181
|
+
documentCache;
|
|
182
|
+
webFingerHandle;
|
|
183
|
+
webFingerHandleDuration;
|
|
184
|
+
collectionRequest;
|
|
185
|
+
collectionDispatchDuration;
|
|
186
|
+
collectionPageItems;
|
|
187
|
+
collectionTotalItems;
|
|
188
|
+
constructor(meterProvider) {
|
|
189
|
+
const meter = meterProvider.getMeter(name, version);
|
|
190
|
+
this.deliverySent = meter.createCounter("activitypub.delivery.sent", {
|
|
191
|
+
description: "ActivityPub delivery attempts.",
|
|
192
|
+
unit: "{attempt}"
|
|
193
|
+
});
|
|
194
|
+
this.deliveryPermanentFailure = meter.createCounter("activitypub.delivery.permanent_failure", {
|
|
195
|
+
description: "ActivityPub deliveries abandoned as permanent failures.",
|
|
196
|
+
unit: "{failure}"
|
|
197
|
+
});
|
|
198
|
+
this.signatureVerificationFailure = meter.createCounter("activitypub.signature.verification_failure", {
|
|
199
|
+
description: "ActivityPub signature verification failures.",
|
|
200
|
+
unit: "{failure}"
|
|
201
|
+
});
|
|
202
|
+
this.signatureVerificationDuration = meter.createHistogram("activitypub.signature.verification.duration", {
|
|
203
|
+
description: "Duration of ActivityPub signature verification, including local key lookup and remote key fetches.",
|
|
204
|
+
unit: "ms",
|
|
205
|
+
advice: { explicitBucketBoundaries: [
|
|
206
|
+
.1,
|
|
207
|
+
.25,
|
|
208
|
+
.5,
|
|
209
|
+
1,
|
|
210
|
+
2.5,
|
|
211
|
+
5,
|
|
212
|
+
10,
|
|
213
|
+
25,
|
|
214
|
+
50,
|
|
215
|
+
100,
|
|
216
|
+
250,
|
|
217
|
+
500,
|
|
218
|
+
1e3
|
|
219
|
+
] }
|
|
220
|
+
});
|
|
221
|
+
this.signatureKeyFetchDuration = meter.createHistogram("activitypub.signature.key_fetch.duration", {
|
|
222
|
+
description: "Duration of public key lookup performed during ActivityPub signature verification.",
|
|
223
|
+
unit: "ms"
|
|
224
|
+
});
|
|
225
|
+
this.deliveryDuration = meter.createHistogram("activitypub.delivery.duration", {
|
|
226
|
+
description: "Duration of ActivityPub delivery attempts.",
|
|
227
|
+
unit: "ms"
|
|
228
|
+
});
|
|
229
|
+
this.inboxProcessingDuration = meter.createHistogram("activitypub.inbox.processing_duration", {
|
|
230
|
+
description: "Duration of ActivityPub inbox listener processing.",
|
|
231
|
+
unit: "ms"
|
|
232
|
+
});
|
|
233
|
+
this.httpServerRequestCount = meter.createCounter("fedify.http.server.request.count", {
|
|
234
|
+
description: "HTTP requests handled by Federation.fetch().",
|
|
235
|
+
unit: "{request}"
|
|
236
|
+
});
|
|
237
|
+
this.httpServerRequestDuration = meter.createHistogram("fedify.http.server.request.duration", {
|
|
238
|
+
description: "Duration of HTTP requests handled by Federation.fetch().",
|
|
239
|
+
unit: "ms",
|
|
240
|
+
advice: { explicitBucketBoundaries: [
|
|
241
|
+
5,
|
|
242
|
+
10,
|
|
243
|
+
25,
|
|
244
|
+
50,
|
|
245
|
+
75,
|
|
246
|
+
100,
|
|
247
|
+
250,
|
|
248
|
+
500,
|
|
249
|
+
750,
|
|
250
|
+
1e3,
|
|
251
|
+
2500,
|
|
252
|
+
5e3,
|
|
253
|
+
7500,
|
|
254
|
+
1e4
|
|
255
|
+
] }
|
|
256
|
+
});
|
|
257
|
+
this.queueTaskEnqueued = meter.createCounter("fedify.queue.task.enqueued", {
|
|
258
|
+
description: "Tasks Fedify enqueued for inbox, outbox, or fanout work.",
|
|
259
|
+
unit: "{task}"
|
|
260
|
+
});
|
|
261
|
+
this.queueTaskStarted = meter.createCounter("fedify.queue.task.started", {
|
|
262
|
+
description: "Tasks Fedify began processing as a queue worker.",
|
|
263
|
+
unit: "{task}"
|
|
264
|
+
});
|
|
265
|
+
this.queueTaskCompleted = meter.createCounter("fedify.queue.task.completed", {
|
|
266
|
+
description: "Queue tasks Fedify finished processing without throwing.",
|
|
267
|
+
unit: "{task}"
|
|
268
|
+
});
|
|
269
|
+
this.queueTaskFailed = meter.createCounter("fedify.queue.task.failed", {
|
|
270
|
+
description: "Queue tasks Fedify abandoned because processing threw.",
|
|
271
|
+
unit: "{task}"
|
|
272
|
+
});
|
|
273
|
+
this.queueTaskDuration = meter.createHistogram("fedify.queue.task.duration", {
|
|
274
|
+
description: "Duration of queue task processing in Fedify workers.",
|
|
275
|
+
unit: "ms",
|
|
276
|
+
advice: { explicitBucketBoundaries: [
|
|
277
|
+
5,
|
|
278
|
+
10,
|
|
279
|
+
25,
|
|
280
|
+
50,
|
|
281
|
+
75,
|
|
282
|
+
100,
|
|
283
|
+
250,
|
|
284
|
+
500,
|
|
285
|
+
750,
|
|
286
|
+
1e3,
|
|
287
|
+
2500,
|
|
288
|
+
5e3,
|
|
289
|
+
7500,
|
|
290
|
+
1e4
|
|
291
|
+
] }
|
|
292
|
+
});
|
|
293
|
+
this.queueTaskInFlight = meter.createUpDownCounter("fedify.queue.task.in_flight", {
|
|
294
|
+
description: "Queue tasks currently being processed in this Fedify process.",
|
|
295
|
+
unit: "{task}"
|
|
296
|
+
});
|
|
297
|
+
this.queueDepth = meter.createObservableGauge("fedify.queue.depth", {
|
|
298
|
+
description: "Messages waiting in configured Fedify queues, as reported by the queue backend.",
|
|
299
|
+
unit: "{message}"
|
|
300
|
+
});
|
|
301
|
+
this.fanoutRecipients = meter.createHistogram("activitypub.fanout.recipients", {
|
|
302
|
+
description: "Number of recipient inboxes produced by an ActivityPub fanout task.",
|
|
303
|
+
unit: "{recipient}"
|
|
304
|
+
});
|
|
305
|
+
this.inboxActivity = meter.createCounter("activitypub.inbox.activity", {
|
|
306
|
+
description: "ActivityPub activities observed at the inbox lifecycle level: queued, processed, retried, rejected, or abandoned.",
|
|
307
|
+
unit: "{activity}"
|
|
308
|
+
});
|
|
309
|
+
this.outboxActivity = meter.createCounter("activitypub.outbox.activity", {
|
|
310
|
+
description: "ActivityPub activities observed at the outbox lifecycle level: queued, retried, or abandoned. Per-recipient delivery counters live on `activitypub.delivery.*`.",
|
|
311
|
+
unit: "{activity}"
|
|
312
|
+
});
|
|
313
|
+
this.circuitBreakerStateChange = meter.createCounter("activitypub.circuit_breaker.state_change", {
|
|
314
|
+
description: "Outbound ActivityPub delivery circuit breaker changes.",
|
|
315
|
+
unit: "{change}"
|
|
316
|
+
});
|
|
317
|
+
this.keyLookup = meter.createCounter("activitypub.key.lookup", {
|
|
318
|
+
description: "Public-key lookup attempts performed by Fedify, including both cache hits and remote fetches.",
|
|
319
|
+
unit: "{lookup}"
|
|
320
|
+
});
|
|
321
|
+
this.keyLookupDuration = meter.createHistogram("activitypub.key.lookup.duration", {
|
|
322
|
+
description: "Duration of public-key lookups performed by Fedify, including any remote fetch.",
|
|
323
|
+
unit: "ms",
|
|
324
|
+
advice: { explicitBucketBoundaries: [
|
|
325
|
+
5,
|
|
326
|
+
10,
|
|
327
|
+
25,
|
|
328
|
+
50,
|
|
329
|
+
75,
|
|
330
|
+
100,
|
|
331
|
+
250,
|
|
332
|
+
500,
|
|
333
|
+
750,
|
|
334
|
+
1e3,
|
|
335
|
+
2500,
|
|
336
|
+
5e3,
|
|
337
|
+
7500,
|
|
338
|
+
1e4
|
|
339
|
+
] }
|
|
340
|
+
});
|
|
341
|
+
this.documentFetch = meter.createCounter("activitypub.document.fetch", {
|
|
342
|
+
description: "Remote JSON-LD document loader invocations made by Fedify-wrapped loaders.",
|
|
343
|
+
unit: "{fetch}"
|
|
344
|
+
});
|
|
345
|
+
this.documentFetchDuration = meter.createHistogram("activitypub.document.fetch.duration", {
|
|
346
|
+
description: "Duration of remote JSON-LD document loader invocations made by Fedify-wrapped loaders.",
|
|
347
|
+
unit: "ms",
|
|
348
|
+
advice: { explicitBucketBoundaries: [
|
|
349
|
+
5,
|
|
350
|
+
10,
|
|
351
|
+
25,
|
|
352
|
+
50,
|
|
353
|
+
75,
|
|
354
|
+
100,
|
|
355
|
+
250,
|
|
356
|
+
500,
|
|
357
|
+
750,
|
|
358
|
+
1e3,
|
|
359
|
+
2500,
|
|
360
|
+
5e3,
|
|
361
|
+
7500,
|
|
362
|
+
1e4
|
|
363
|
+
] }
|
|
364
|
+
});
|
|
365
|
+
this.documentCache = meter.createCounter("activitypub.document.cache", {
|
|
366
|
+
description: "KV-backed document loader cache lookups, with `hit` or `miss` classification.",
|
|
367
|
+
unit: "{lookup}"
|
|
368
|
+
});
|
|
369
|
+
this.webFingerHandle = meter.createCounter("webfinger.handle", {
|
|
370
|
+
description: "Incoming WebFinger requests handled by Fedify, classified by terminal outcome.",
|
|
371
|
+
unit: "{request}"
|
|
372
|
+
});
|
|
373
|
+
this.webFingerHandleDuration = meter.createHistogram("webfinger.handle.duration", {
|
|
374
|
+
description: "Duration of incoming WebFinger request handling in Fedify.",
|
|
375
|
+
unit: "ms",
|
|
376
|
+
advice: { explicitBucketBoundaries: [
|
|
377
|
+
5,
|
|
378
|
+
10,
|
|
379
|
+
25,
|
|
380
|
+
50,
|
|
381
|
+
75,
|
|
382
|
+
100,
|
|
383
|
+
250,
|
|
384
|
+
500,
|
|
385
|
+
750,
|
|
386
|
+
1e3,
|
|
387
|
+
2500,
|
|
388
|
+
5e3,
|
|
389
|
+
7500,
|
|
390
|
+
1e4
|
|
391
|
+
] }
|
|
392
|
+
});
|
|
393
|
+
this.collectionRequest = meter.createCounter("activitypub.collection.request", {
|
|
394
|
+
description: "ActivityPub collection and collection-page requests handled by Fedify.",
|
|
395
|
+
unit: "{request}"
|
|
396
|
+
});
|
|
397
|
+
this.collectionDispatchDuration = meter.createHistogram("activitypub.collection.dispatch.duration", {
|
|
398
|
+
description: "Duration of ActivityPub collection dispatcher callbacks.",
|
|
399
|
+
unit: "ms",
|
|
400
|
+
advice: { explicitBucketBoundaries: [
|
|
401
|
+
5,
|
|
402
|
+
10,
|
|
403
|
+
25,
|
|
404
|
+
50,
|
|
405
|
+
75,
|
|
406
|
+
100,
|
|
407
|
+
250,
|
|
408
|
+
500,
|
|
409
|
+
750,
|
|
410
|
+
1e3,
|
|
411
|
+
2500,
|
|
412
|
+
5e3,
|
|
413
|
+
7500,
|
|
414
|
+
1e4
|
|
415
|
+
] }
|
|
416
|
+
});
|
|
417
|
+
this.collectionPageItems = meter.createHistogram("activitypub.collection.page.items", {
|
|
418
|
+
description: "Number of items Fedify materialized for an ActivityPub collection response.",
|
|
419
|
+
unit: "{item}"
|
|
420
|
+
});
|
|
421
|
+
this.collectionTotalItems = meter.createHistogram("activitypub.collection.total_items", {
|
|
422
|
+
description: "Total item count reported by ActivityPub collection counters.",
|
|
423
|
+
unit: "{item}"
|
|
424
|
+
});
|
|
425
|
+
}
|
|
426
|
+
recordDelivery(inbox, durationMs, success, activityType) {
|
|
427
|
+
const deliveryAttributes = {
|
|
428
|
+
"activitypub.remote.host": getRemoteHost(inbox),
|
|
429
|
+
"activitypub.delivery.success": success
|
|
430
|
+
};
|
|
431
|
+
if (activityType != null) deliveryAttributes["activitypub.activity.type"] = activityType;
|
|
432
|
+
this.deliverySent.add(1, deliveryAttributes);
|
|
433
|
+
this.deliveryDuration.record(durationMs, deliveryAttributes);
|
|
434
|
+
}
|
|
435
|
+
recordPermanentFailure(inbox, statusCode) {
|
|
436
|
+
this.deliveryPermanentFailure.add(1, {
|
|
437
|
+
"activitypub.remote.host": getRemoteHost(inbox),
|
|
438
|
+
"http.response.status_code": statusCode
|
|
439
|
+
});
|
|
440
|
+
}
|
|
441
|
+
recordSignatureVerificationFailure(reason, remoteHost) {
|
|
442
|
+
const attributes = { "activitypub.verification.failure_reason": reason };
|
|
443
|
+
if (remoteHost != null) attributes["activitypub.remote.host"] = remoteHost;
|
|
444
|
+
this.signatureVerificationFailure.add(1, attributes);
|
|
445
|
+
}
|
|
446
|
+
recordSignatureVerificationDuration(durationMs, kind, result, extra = {}) {
|
|
447
|
+
const attributes = {
|
|
448
|
+
"activitypub.signature.kind": kind,
|
|
449
|
+
"activitypub.signature.result": result
|
|
450
|
+
};
|
|
451
|
+
if (extra.algorithm != null) attributes["http_signatures.algorithm"] = extra.algorithm;
|
|
452
|
+
if (extra.failureReason != null) attributes["http_signatures.failure_reason"] = extra.failureReason;
|
|
453
|
+
if (extra.ldType != null) attributes["ld_signatures.type"] = extra.ldType;
|
|
454
|
+
if (extra.cryptosuite != null) attributes["object_integrity_proofs.cryptosuite"] = extra.cryptosuite;
|
|
455
|
+
this.signatureVerificationDuration.record(durationMs, attributes);
|
|
456
|
+
}
|
|
457
|
+
recordSignatureKeyFetchDuration(durationMs, kind, result) {
|
|
458
|
+
this.signatureKeyFetchDuration.record(durationMs, {
|
|
459
|
+
"activitypub.signature.kind": kind,
|
|
460
|
+
"activitypub.signature.key_fetch.result": result
|
|
461
|
+
});
|
|
462
|
+
}
|
|
463
|
+
recordInboxProcessingDuration(activityType, durationMs) {
|
|
464
|
+
this.inboxProcessingDuration.record(durationMs, { "activitypub.activity.type": activityType });
|
|
465
|
+
}
|
|
466
|
+
recordHttpServerRequest(method, endpoint, durationMs, options = {}) {
|
|
467
|
+
const attributes = {
|
|
468
|
+
"http.request.method": normalizeHttpMethod(method),
|
|
469
|
+
"fedify.endpoint": endpoint
|
|
470
|
+
};
|
|
471
|
+
if (options.statusCode != null) attributes["http.response.status_code"] = options.statusCode;
|
|
472
|
+
if (options.routeTemplate != null) attributes["fedify.route.template"] = options.routeTemplate;
|
|
473
|
+
this.httpServerRequestCount.add(1, attributes);
|
|
474
|
+
this.httpServerRequestDuration.record(durationMs, attributes);
|
|
475
|
+
}
|
|
476
|
+
recordQueueTaskEnqueued(common, attempt) {
|
|
477
|
+
const attributes = buildQueueTaskAttributes(common);
|
|
478
|
+
attributes["fedify.queue.task.attempt"] = attempt;
|
|
479
|
+
this.queueTaskEnqueued.add(1, attributes);
|
|
480
|
+
}
|
|
481
|
+
recordQueueTaskStarted(common) {
|
|
482
|
+
this.queueTaskStarted.add(1, buildQueueTaskAttributes(common));
|
|
483
|
+
}
|
|
484
|
+
incrementQueueTaskInFlight(common) {
|
|
485
|
+
this.queueTaskInFlight.add(1, buildQueueTaskInFlightAttributes(common));
|
|
486
|
+
}
|
|
487
|
+
decrementQueueTaskInFlight(common) {
|
|
488
|
+
this.queueTaskInFlight.add(-1, buildQueueTaskInFlightAttributes(common));
|
|
489
|
+
}
|
|
490
|
+
recordQueueTaskOutcome(common, result, durationMs) {
|
|
491
|
+
const attributes = buildQueueTaskAttributes(common);
|
|
492
|
+
attributes["fedify.queue.task.result"] = result;
|
|
493
|
+
if (result === "completed") this.queueTaskCompleted.add(1, attributes);
|
|
494
|
+
else if (result === "failed") this.queueTaskFailed.add(1, attributes);
|
|
495
|
+
this.queueTaskDuration.record(durationMs, attributes);
|
|
496
|
+
}
|
|
497
|
+
recordFanoutRecipients(recipientCount, activityType) {
|
|
498
|
+
const attributes = {};
|
|
499
|
+
if (activityType != null) attributes["activitypub.activity.type"] = activityType;
|
|
500
|
+
this.fanoutRecipients.record(recipientCount, attributes);
|
|
501
|
+
}
|
|
502
|
+
recordInboxActivity(result, activityType) {
|
|
503
|
+
this.inboxActivity.add(1, buildActivityLifecycleAttributes(result, activityType));
|
|
504
|
+
}
|
|
505
|
+
recordOutboxActivity(result, activityType) {
|
|
506
|
+
this.outboxActivity.add(1, buildActivityLifecycleAttributes(result, activityType));
|
|
507
|
+
}
|
|
508
|
+
recordCircuitBreakerStateChange(remoteHost, state) {
|
|
509
|
+
this.circuitBreakerStateChange.add(1, {
|
|
510
|
+
"activitypub.remote.host": remoteHost,
|
|
511
|
+
"activitypub.circuit_breaker.state": state
|
|
512
|
+
});
|
|
513
|
+
}
|
|
514
|
+
recordKeyLookup(attrs) {
|
|
515
|
+
const attributes = {
|
|
516
|
+
"activitypub.lookup.kind": "public_key",
|
|
517
|
+
"activitypub.lookup.result": attrs.result,
|
|
518
|
+
"activitypub.cache.enabled": attrs.cacheEnabled
|
|
519
|
+
};
|
|
520
|
+
if (attrs.remoteUrl != null) attributes["activitypub.remote.host"] = getRemoteHost(attrs.remoteUrl);
|
|
521
|
+
if (attrs.statusCode != null) attributes["http.response.status_code"] = attrs.statusCode;
|
|
522
|
+
this.keyLookup.add(1, attributes);
|
|
523
|
+
this.keyLookupDuration.record(attrs.durationMs, attributes);
|
|
524
|
+
}
|
|
525
|
+
recordDocumentFetch(attrs) {
|
|
526
|
+
const attributes = {
|
|
527
|
+
"activitypub.lookup.kind": attrs.kind,
|
|
528
|
+
"activitypub.lookup.result": attrs.result
|
|
529
|
+
};
|
|
530
|
+
if (attrs.remoteUrl != null) attributes["activitypub.remote.host"] = getRemoteHost(attrs.remoteUrl);
|
|
531
|
+
if (attrs.cacheEnabled != null) attributes["activitypub.cache.enabled"] = attrs.cacheEnabled;
|
|
532
|
+
if (attrs.statusCode != null) attributes["http.response.status_code"] = attrs.statusCode;
|
|
533
|
+
this.documentFetch.add(1, attributes);
|
|
534
|
+
this.documentFetchDuration.record(attrs.durationMs, attributes);
|
|
535
|
+
}
|
|
536
|
+
recordDocumentCache(attrs) {
|
|
537
|
+
const attributes = {
|
|
538
|
+
"activitypub.lookup.kind": attrs.kind,
|
|
539
|
+
"activitypub.lookup.result": attrs.result
|
|
540
|
+
};
|
|
541
|
+
if (attrs.remoteUrl != null) attributes["activitypub.remote.host"] = getRemoteHost(attrs.remoteUrl);
|
|
542
|
+
this.documentCache.add(1, attributes);
|
|
543
|
+
}
|
|
544
|
+
recordWebFingerHandle(attrs) {
|
|
545
|
+
const attributes = { "webfinger.handle.result": attrs.result };
|
|
546
|
+
if (attrs.scheme != null) attributes["webfinger.resource.scheme"] = attrs.scheme;
|
|
547
|
+
if (attrs.statusCode != null) attributes["http.response.status_code"] = attrs.statusCode;
|
|
548
|
+
this.webFingerHandle.add(1, attributes);
|
|
549
|
+
this.webFingerHandleDuration.record(attrs.durationMs, attributes);
|
|
550
|
+
}
|
|
551
|
+
recordCollectionRequest(attrs) {
|
|
552
|
+
this.collectionRequest.add(1, buildCollectionAttributes(attrs));
|
|
553
|
+
}
|
|
554
|
+
recordCollectionDispatchDuration(durationMs, attrs) {
|
|
555
|
+
this.collectionDispatchDuration.record(durationMs, buildCollectionAttributes(attrs));
|
|
556
|
+
}
|
|
557
|
+
recordCollectionPageItems(itemCount, attrs) {
|
|
558
|
+
this.collectionPageItems.record(itemCount, buildCollectionAttributes(attrs));
|
|
559
|
+
}
|
|
560
|
+
recordCollectionTotalItems(totalItems, attrs) {
|
|
561
|
+
this.collectionTotalItems.record(totalItems, buildCollectionAttributes(attrs));
|
|
562
|
+
}
|
|
563
|
+
};
|
|
564
|
+
function buildCollectionAttributes(attrs) {
|
|
565
|
+
const attributes = {
|
|
566
|
+
"activitypub.collection.kind": attrs.kind,
|
|
567
|
+
"activitypub.collection.page": attrs.page,
|
|
568
|
+
"activitypub.collection.result": attrs.result,
|
|
569
|
+
"fedify.collection.dispatcher": attrs.dispatcher
|
|
570
|
+
};
|
|
571
|
+
if (attrs.statusCode != null) attributes["http.response.status_code"] = attrs.statusCode;
|
|
572
|
+
return attributes;
|
|
573
|
+
}
|
|
574
|
+
function buildActivityLifecycleAttributes(result, activityType) {
|
|
575
|
+
const attributes = { "activitypub.processing.result": result };
|
|
576
|
+
if (activityType != null) attributes["activitypub.activity.type"] = activityType;
|
|
577
|
+
return attributes;
|
|
578
|
+
}
|
|
579
|
+
function buildQueueTaskAttributes(common) {
|
|
580
|
+
const attributes = { "fedify.queue.role": common.role };
|
|
581
|
+
const backend = getQueueBackend(common.queue);
|
|
582
|
+
if (backend != null) attributes["fedify.queue.backend"] = backend;
|
|
583
|
+
const nativeRetrial = common.queue?.nativeRetrial;
|
|
584
|
+
if (typeof nativeRetrial === "boolean") attributes["fedify.queue.native_retrial"] = nativeRetrial;
|
|
585
|
+
if (common.activityType != null) attributes["activitypub.activity.type"] = common.activityType;
|
|
586
|
+
return attributes;
|
|
587
|
+
}
|
|
588
|
+
function buildQueueTaskInFlightAttributes(common) {
|
|
589
|
+
return buildQueueTaskAttributes({
|
|
590
|
+
role: common.role,
|
|
591
|
+
queue: common.queue
|
|
592
|
+
});
|
|
593
|
+
}
|
|
594
|
+
/**
|
|
595
|
+
* Returns the constructor name of the given message queue, when it is a
|
|
596
|
+
* meaningful identifier. Used as a best-effort `fedify.queue.backend`
|
|
597
|
+
* attribute on queue task metrics; returns `undefined` for plain object
|
|
598
|
+
* literals (whose constructor is `Object`) so the attribute does not appear
|
|
599
|
+
* with a non-informative value.
|
|
600
|
+
* @since 2.3.0
|
|
601
|
+
*/
|
|
602
|
+
function getQueueBackend(queue) {
|
|
603
|
+
const name = queue?.constructor?.name;
|
|
604
|
+
if (name == null || name === "" || name === "Object") return void 0;
|
|
605
|
+
return name;
|
|
606
|
+
}
|
|
607
|
+
/**
|
|
608
|
+
* Registers a callback for observing queue backend depth.
|
|
609
|
+
* @since 2.3.0
|
|
610
|
+
*/
|
|
611
|
+
function registerQueueDepthGauge(meterProvider, entries, options = {}) {
|
|
612
|
+
const uniqueQueues = /* @__PURE__ */ new Map();
|
|
613
|
+
for (const { role, queue } of entries) {
|
|
614
|
+
if (queue?.getDepth == null) continue;
|
|
615
|
+
const roles = uniqueQueues.get(queue);
|
|
616
|
+
if (roles == null) uniqueQueues.set(queue, [role]);
|
|
617
|
+
else if (!roles.includes(role)) roles.push(role);
|
|
618
|
+
}
|
|
619
|
+
if (uniqueQueues.size < 1) return;
|
|
620
|
+
const queueEntries = Array.from(uniqueQueues.entries());
|
|
621
|
+
getFederationMetrics(meterProvider).queueDepth.addCallback(async (observableResult) => {
|
|
622
|
+
await Promise.all(queueEntries.map(async ([queue, roles]) => {
|
|
623
|
+
let depth;
|
|
624
|
+
try {
|
|
625
|
+
depth = await queue.getDepth();
|
|
626
|
+
} catch {
|
|
627
|
+
return;
|
|
628
|
+
}
|
|
629
|
+
if (depth == null) return;
|
|
630
|
+
const attributes = buildQueueDepthAttributes(queue, roles, options);
|
|
631
|
+
observableResult.observe(depth.queued, {
|
|
632
|
+
...attributes,
|
|
633
|
+
"fedify.queue.depth.state": "queued"
|
|
634
|
+
});
|
|
635
|
+
if (depth.ready != null) observableResult.observe(depth.ready, {
|
|
636
|
+
...attributes,
|
|
637
|
+
"fedify.queue.depth.state": "ready"
|
|
638
|
+
});
|
|
639
|
+
if (depth.delayed != null) observableResult.observe(depth.delayed, {
|
|
640
|
+
...attributes,
|
|
641
|
+
"fedify.queue.depth.state": "delayed"
|
|
642
|
+
});
|
|
643
|
+
}));
|
|
644
|
+
});
|
|
645
|
+
}
|
|
646
|
+
function buildQueueDepthAttributes(queue, roles, options) {
|
|
647
|
+
const sortedRoles = roles.toSorted();
|
|
648
|
+
const role = sortedRoles.length === 1 ? sortedRoles[0] : "shared";
|
|
649
|
+
const attributes = { "fedify.queue.role": role };
|
|
650
|
+
if (options.sourceId != null) attributes["fedify.federation.instance_id"] = options.sourceId;
|
|
651
|
+
if (role === "shared") attributes["fedify.queue.roles"] = sortedRoles.join(",");
|
|
652
|
+
const backend = getQueueBackend(queue);
|
|
653
|
+
if (backend != null) attributes["fedify.queue.backend"] = backend;
|
|
654
|
+
const nativeRetrial = queue.nativeRetrial;
|
|
655
|
+
if (typeof nativeRetrial === "boolean") attributes["fedify.queue.native_retrial"] = nativeRetrial;
|
|
656
|
+
return attributes;
|
|
657
|
+
}
|
|
658
|
+
/**
|
|
659
|
+
* Records `fedify.queue.task.enqueued` for an outgoing outbox enqueue and,
|
|
660
|
+
* for the initial attempt, also records
|
|
661
|
+
* `activitypub.outbox.activity{queued}`.
|
|
662
|
+
*
|
|
663
|
+
* Both `Context.sendActivity()` and `OutboxContext.forwardActivity()` enqueue
|
|
664
|
+
* outbox messages with the same metric attributes (role, queue, activity
|
|
665
|
+
* type, attempt), so they share this helper rather than each defining a local
|
|
666
|
+
* closure. Retry enqueues (attempt > 0) intentionally do not record a
|
|
667
|
+
* second `activitypub.outbox.activity{queued}`; retries are reported as
|
|
668
|
+
* `result=retried` from the retry-scheduling site, which has the failure
|
|
669
|
+
* context.
|
|
670
|
+
* @since 2.3.0
|
|
671
|
+
*/
|
|
672
|
+
function recordOutboxEnqueue(meterProvider, outboxQueue, message) {
|
|
673
|
+
const metrics = getFederationMetrics(meterProvider);
|
|
674
|
+
metrics.recordQueueTaskEnqueued({
|
|
675
|
+
role: "outbox",
|
|
676
|
+
queue: outboxQueue,
|
|
677
|
+
activityType: message.activityType
|
|
678
|
+
}, message.attempt);
|
|
679
|
+
if (message.attempt === 0) metrics.recordOutboxActivity("queued", message.activityType);
|
|
680
|
+
}
|
|
681
|
+
/**
|
|
682
|
+
* Records `activitypub.fanout.recipients` with the number of recipient
|
|
683
|
+
* inboxes a single fanout produced. The histogram is unitless count
|
|
684
|
+
* (one measurement per fanout enqueue). Recipient URLs are deliberately
|
|
685
|
+
* not recorded; only the activity type, when known.
|
|
686
|
+
* @since 2.3.0
|
|
687
|
+
*/
|
|
688
|
+
function recordFanoutRecipients(meterProvider, recipientCount, activityType) {
|
|
689
|
+
getFederationMetrics(meterProvider).recordFanoutRecipients(recipientCount, activityType);
|
|
690
|
+
}
|
|
691
|
+
/**
|
|
692
|
+
* Records one `activitypub.inbox.activity` measurement. The
|
|
693
|
+
* `activitypub.processing.result` attribute is always present;
|
|
694
|
+
* `activitypub.activity.type` is recorded only when Fedify already knows
|
|
695
|
+
* the activity type.
|
|
696
|
+
* @since 2.3.0
|
|
697
|
+
*/
|
|
698
|
+
function recordInboxActivity(meterProvider, result, activityType) {
|
|
699
|
+
getFederationMetrics(meterProvider).recordInboxActivity(result, activityType);
|
|
700
|
+
}
|
|
701
|
+
/**
|
|
702
|
+
* Records one `activitypub.outbox.activity` measurement. The
|
|
703
|
+
* `activitypub.processing.result` attribute is always present;
|
|
704
|
+
* `activitypub.activity.type` is recorded only when Fedify already knows
|
|
705
|
+
* the activity type (it is always known for outbox lifecycle events).
|
|
706
|
+
* @since 2.3.0
|
|
707
|
+
*/
|
|
708
|
+
function recordOutboxActivity(meterProvider, result, activityType) {
|
|
709
|
+
getFederationMetrics(meterProvider).recordOutboxActivity(result, activityType);
|
|
710
|
+
}
|
|
711
|
+
/**
|
|
712
|
+
* Records one outbound delivery circuit breaker state transition.
|
|
713
|
+
* @since 2.3.0
|
|
714
|
+
*/
|
|
715
|
+
function recordCircuitBreakerStateChange(meterProvider, remoteHost, state) {
|
|
716
|
+
getFederationMetrics(meterProvider).recordCircuitBreakerStateChange(remoteHost, state);
|
|
717
|
+
}
|
|
718
|
+
/**
|
|
719
|
+
* Records one measurement on `activitypub.key.lookup` (counter) and
|
|
720
|
+
* `activitypub.key.lookup.duration` (histogram) for a public-key lookup.
|
|
721
|
+
*
|
|
722
|
+
* `activitypub.lookup.kind` is always recorded as `public_key`; the result
|
|
723
|
+
* classification, remote host, HTTP status code (when an HTTP response was
|
|
724
|
+
* received), and `activitypub.cache.enabled` are recorded as attributes on
|
|
725
|
+
* both measurements. Full key URLs and key IDs are deliberately omitted to
|
|
726
|
+
* keep cardinality bounded.
|
|
727
|
+
* @since 2.3.0
|
|
728
|
+
*/
|
|
729
|
+
function recordKeyLookup(meterProvider, attrs) {
|
|
730
|
+
getFederationMetrics(meterProvider).recordKeyLookup(attrs);
|
|
731
|
+
}
|
|
732
|
+
/**
|
|
733
|
+
* Records one measurement each on `activitypub.document.fetch` (counter)
|
|
734
|
+
* and `activitypub.document.fetch.duration` (histogram) for one remote
|
|
735
|
+
* JSON-LD document loader invocation, with bounded
|
|
736
|
+
* `activitypub.lookup.kind` and `activitypub.lookup.result` attributes
|
|
737
|
+
* plus the optional remote-host, cache-enabled, and HTTP status-code
|
|
738
|
+
* attributes. Counter and histogram are always recorded together so
|
|
739
|
+
* aggregate rate and latency views stay in sync.
|
|
740
|
+
* @since 2.3.0
|
|
741
|
+
*/
|
|
742
|
+
function recordDocumentFetch(meterProvider, attrs) {
|
|
743
|
+
getFederationMetrics(meterProvider).recordDocumentFetch(attrs);
|
|
744
|
+
}
|
|
745
|
+
/**
|
|
746
|
+
* Records one `activitypub.document.cache` measurement, classifying the
|
|
747
|
+
* lookup as `hit` (the cache returned an entry) or `miss` (the cache was
|
|
748
|
+
* consulted and returned nothing, prompting a delegate fetch).
|
|
749
|
+
* @since 2.3.0
|
|
750
|
+
*/
|
|
751
|
+
function recordDocumentCache(meterProvider, attrs) {
|
|
752
|
+
getFederationMetrics(meterProvider).recordDocumentCache(attrs);
|
|
753
|
+
}
|
|
754
|
+
/**
|
|
755
|
+
* Records one measurement on `webfinger.handle` (counter) and
|
|
756
|
+
* `webfinger.handle.duration` (histogram) for an incoming WebFinger
|
|
757
|
+
* request handled by Fedify. Counter and histogram are always recorded
|
|
758
|
+
* together, with `webfinger.handle.result` set to one of `resolved`,
|
|
759
|
+
* `invalid`, `not_found`, `tombstoned`, or `error`. The queried
|
|
760
|
+
* resource string is deliberately excluded; it remains on the
|
|
761
|
+
* `webfinger.handle` span for trace-level investigation.
|
|
762
|
+
* @since 2.3.0
|
|
763
|
+
*/
|
|
764
|
+
function recordWebFingerHandle(meterProvider, attrs) {
|
|
765
|
+
getFederationMetrics(meterProvider).recordWebFingerHandle(attrs);
|
|
766
|
+
}
|
|
767
|
+
/**
|
|
768
|
+
* Records one `activitypub.collection.request` measurement for a
|
|
769
|
+
* collection or collection-page request handled by Fedify.
|
|
770
|
+
* @since 2.3.0
|
|
771
|
+
*/
|
|
772
|
+
function recordCollectionRequest(meterProvider, attrs) {
|
|
773
|
+
getFederationMetrics(meterProvider).recordCollectionRequest(attrs);
|
|
774
|
+
}
|
|
775
|
+
/**
|
|
776
|
+
* Records one `activitypub.collection.dispatch.duration` measurement for a
|
|
777
|
+
* collection dispatcher callback invocation.
|
|
778
|
+
* @since 2.3.0
|
|
779
|
+
*/
|
|
780
|
+
function recordCollectionDispatchDuration(meterProvider, durationMs, attrs) {
|
|
781
|
+
getFederationMetrics(meterProvider).recordCollectionDispatchDuration(durationMs, attrs);
|
|
782
|
+
}
|
|
783
|
+
/**
|
|
784
|
+
* Records one `activitypub.collection.page.items` measurement when Fedify
|
|
785
|
+
* has materialized collection items in memory.
|
|
786
|
+
* @since 2.3.0
|
|
787
|
+
*/
|
|
788
|
+
function recordCollectionPageItems(meterProvider, itemCount, attrs) {
|
|
789
|
+
getFederationMetrics(meterProvider).recordCollectionPageItems(itemCount, attrs);
|
|
790
|
+
}
|
|
791
|
+
/**
|
|
792
|
+
* Records one `activitypub.collection.total_items` measurement when a
|
|
793
|
+
* collection counter has already reported a total item count.
|
|
794
|
+
* @since 2.3.0
|
|
795
|
+
*/
|
|
796
|
+
function recordCollectionTotalItems(meterProvider, totalItems, attrs) {
|
|
797
|
+
getFederationMetrics(meterProvider).recordCollectionTotalItems(totalItems, attrs);
|
|
798
|
+
}
|
|
799
|
+
/**
|
|
800
|
+
* Classifies a thrown value from a key or document fetch into the bounded
|
|
801
|
+
* {@link LookupResult} taxonomy and, when an HTTP response was received,
|
|
802
|
+
* surfaces its status code.
|
|
803
|
+
*
|
|
804
|
+
* - `FetchError` with a `Response` whose status is `404` or `410`:
|
|
805
|
+
* `result=not_found` and the response status code.
|
|
806
|
+
* - `FetchError` with any other `Response`: `result=error` and the
|
|
807
|
+
* response status code.
|
|
808
|
+
* - `FetchError` without a `Response`: `result=network_error`.
|
|
809
|
+
* - An `AbortError` (typically from a cancelled fetch): `result=network_error`.
|
|
810
|
+
* - A bare `TypeError` (the shape native `fetch()` raises on DNS, connect,
|
|
811
|
+
* and TLS failures before any response is observed):
|
|
812
|
+
* `result=network_error`.
|
|
813
|
+
* - Any other value: `result=error`.
|
|
814
|
+
* @since 2.3.0
|
|
815
|
+
*/
|
|
816
|
+
function classifyFetchError(error) {
|
|
817
|
+
if (error instanceof _fedify_vocab_runtime.FetchError) {
|
|
818
|
+
if (error.response != null) {
|
|
819
|
+
const status = error.response.status;
|
|
820
|
+
return {
|
|
821
|
+
result: status === 404 || status === 410 ? "not_found" : "error",
|
|
822
|
+
statusCode: status
|
|
823
|
+
};
|
|
824
|
+
}
|
|
825
|
+
return { result: "network_error" };
|
|
826
|
+
}
|
|
827
|
+
if (isAbortError$1(error)) return { result: "network_error" };
|
|
828
|
+
if (error instanceof TypeError) return { result: "network_error" };
|
|
829
|
+
return { result: "error" };
|
|
830
|
+
}
|
|
831
|
+
/**
|
|
832
|
+
* Wraps a {@link DocumentLoader} so each invocation records one
|
|
833
|
+
* measurement on `activitypub.document.fetch` (counter) and one on
|
|
834
|
+
* `activitypub.document.fetch.duration` (histogram), classifying the
|
|
835
|
+
* outcome via {@link classifyFetchError} when the wrapped loader throws
|
|
836
|
+
* and as `fetched` on success. The wrapper rethrows whatever the
|
|
837
|
+
* wrapped loader throws so caller behavior is unchanged.
|
|
838
|
+
*
|
|
839
|
+
* The wrapper records the host of the requested URL, including any
|
|
840
|
+
* non-default port, on `activitypub.remote.host` when the URL parses; full
|
|
841
|
+
* URLs, paths, and query strings are deliberately excluded to keep
|
|
842
|
+
* cardinality bounded.
|
|
843
|
+
* HTTP status codes are recorded only when the failure carries a
|
|
844
|
+
* `Response` (currently, when the wrapped loader throws a
|
|
845
|
+
* {@link FetchError} with a non-`null` `response`).
|
|
846
|
+
* @since 2.3.0
|
|
847
|
+
*/
|
|
848
|
+
function instrumentDocumentLoader(loader, options) {
|
|
849
|
+
const meterProvider = options.meterProvider;
|
|
850
|
+
if (meterProvider == null) return loader;
|
|
851
|
+
return async (url, opts) => {
|
|
852
|
+
const start = performance.now();
|
|
853
|
+
let remoteUrl;
|
|
854
|
+
try {
|
|
855
|
+
remoteUrl = new URL(url);
|
|
856
|
+
} catch {
|
|
857
|
+
remoteUrl = void 0;
|
|
858
|
+
}
|
|
859
|
+
try {
|
|
860
|
+
const result = await loader(url, opts);
|
|
861
|
+
recordDocumentFetch(meterProvider, {
|
|
862
|
+
durationMs: getDurationMs(start),
|
|
863
|
+
kind: options.kind,
|
|
864
|
+
result: "fetched",
|
|
865
|
+
remoteUrl,
|
|
866
|
+
cacheEnabled: options.cacheEnabled
|
|
867
|
+
});
|
|
868
|
+
return result;
|
|
869
|
+
} catch (error) {
|
|
870
|
+
const classified = classifyFetchError(error);
|
|
871
|
+
recordDocumentFetch(meterProvider, {
|
|
872
|
+
durationMs: getDurationMs(start),
|
|
873
|
+
kind: options.kind,
|
|
874
|
+
result: classified.result,
|
|
875
|
+
remoteUrl,
|
|
876
|
+
cacheEnabled: options.cacheEnabled,
|
|
877
|
+
statusCode: classified.statusCode
|
|
878
|
+
});
|
|
879
|
+
throw error;
|
|
880
|
+
}
|
|
881
|
+
};
|
|
882
|
+
}
|
|
883
|
+
/**
|
|
884
|
+
* Times an awaited public key fetch and records exactly one
|
|
885
|
+
* `activitypub.signature.key_fetch.duration` measurement, classifying the
|
|
886
|
+
* outcome as `hit`, `fetched`, or `error` based on the `cached` flag and
|
|
887
|
+
* whether the returned key is non-null. Errors thrown by the fetch are
|
|
888
|
+
* reported as `error` and rethrown, so verifier behavior is unchanged.
|
|
889
|
+
*
|
|
890
|
+
* Shared by the three signature verifiers (HTTP, Linked Data, Object
|
|
891
|
+
* Integrity Proofs); the only per-call variation is the
|
|
892
|
+
* `activitypub.signature.kind` attribute value.
|
|
893
|
+
* @since 2.3.0
|
|
894
|
+
*/
|
|
895
|
+
async function measureSignatureKeyFetch(meterProvider, kind, fetch) {
|
|
896
|
+
const start = performance.now();
|
|
897
|
+
try {
|
|
898
|
+
const result = await fetch();
|
|
899
|
+
getFederationMetrics(meterProvider).recordSignatureKeyFetchDuration(getDurationMs(start), kind, result.key != null ? result.cached ? "hit" : "fetched" : "error");
|
|
900
|
+
return result;
|
|
901
|
+
} catch (error) {
|
|
902
|
+
getFederationMetrics(meterProvider).recordSignatureKeyFetchDuration(getDurationMs(start), kind, "error");
|
|
903
|
+
throw error;
|
|
904
|
+
}
|
|
905
|
+
}
|
|
906
|
+
/**
|
|
907
|
+
* Whether the given thrown value is an `AbortError`.
|
|
908
|
+
*
|
|
909
|
+
* `processQueuedTask` distinguishes aborted tasks (recorded as
|
|
910
|
+
* `fedify.queue.task.result=aborted`) from other failures so that backend
|
|
911
|
+
* shutdown signals do not inflate the `fedify.queue.task.failed` counter.
|
|
912
|
+
* @since 2.3.0
|
|
913
|
+
*/
|
|
914
|
+
function isAbortError$1(error) {
|
|
915
|
+
if (error == null || typeof error !== "object") return false;
|
|
916
|
+
const name = error.name;
|
|
917
|
+
return typeof name === "string" && name === "AbortError";
|
|
918
|
+
}
|
|
919
|
+
const KNOWN_HTTP_METHODS = new Set([
|
|
920
|
+
"CONNECT",
|
|
921
|
+
"DELETE",
|
|
922
|
+
"GET",
|
|
923
|
+
"HEAD",
|
|
924
|
+
"OPTIONS",
|
|
925
|
+
"PATCH",
|
|
926
|
+
"POST",
|
|
927
|
+
"PUT",
|
|
928
|
+
"QUERY",
|
|
929
|
+
"TRACE"
|
|
930
|
+
]);
|
|
931
|
+
function normalizeHttpMethod(method) {
|
|
932
|
+
const upper = method.toUpperCase();
|
|
933
|
+
return KNOWN_HTTP_METHODS.has(upper) ? upper : "_OTHER";
|
|
934
|
+
}
|
|
935
|
+
const federationMetrics = /* @__PURE__ */ new WeakMap();
|
|
936
|
+
/**
|
|
937
|
+
* Gets the cached Fedify metric instruments for a meter provider.
|
|
938
|
+
* @since 2.3.0
|
|
939
|
+
*/
|
|
940
|
+
function getFederationMetrics(meterProvider = _opentelemetry_api.metrics.getMeterProvider()) {
|
|
941
|
+
let instruments = federationMetrics.get(meterProvider);
|
|
942
|
+
if (instruments == null) {
|
|
943
|
+
instruments = new FederationMetrics(meterProvider);
|
|
944
|
+
federationMetrics.set(meterProvider, instruments);
|
|
945
|
+
}
|
|
946
|
+
return instruments;
|
|
947
|
+
}
|
|
948
|
+
/**
|
|
949
|
+
* Gets the bounded remote host attribute value for a URL.
|
|
950
|
+
* @since 2.3.0
|
|
951
|
+
*/
|
|
952
|
+
function getRemoteHost(url) {
|
|
953
|
+
return url.host;
|
|
954
|
+
}
|
|
955
|
+
/**
|
|
956
|
+
* Gets an elapsed duration in milliseconds from a `performance.now()` value.
|
|
957
|
+
* @since 2.3.0
|
|
958
|
+
*/
|
|
959
|
+
function getDurationMs(start) {
|
|
960
|
+
return Math.max(0, performance.now() - start);
|
|
961
|
+
}
|
|
962
|
+
//#endregion
|
|
155
963
|
//#region src/sig/key.ts
|
|
156
964
|
/**
|
|
157
965
|
* Checks if the given key is valid and supported. No-op if the key is valid,
|
|
@@ -452,24 +1260,48 @@ async function resolveFetchedKey(document, cacheKey, keyId, cls, { documentLoade
|
|
|
452
1260
|
};
|
|
453
1261
|
}
|
|
454
1262
|
async function fetchKeyWithResult(cacheKey, cls, options, onCachedUnavailable, onFetchError) {
|
|
455
|
-
const
|
|
456
|
-
|
|
457
|
-
"sig",
|
|
458
|
-
"key"
|
|
459
|
-
]);
|
|
460
|
-
const keyId = cacheKey.href;
|
|
461
|
-
const keyCache = options.keyCache;
|
|
462
|
-
const cached = await getCachedFetchKey(cacheKey, keyId, cls, keyCache, logger);
|
|
463
|
-
if (cached?.key === null && cached.cached) return await onCachedUnavailable(cacheKey, keyId, keyCache, logger);
|
|
464
|
-
if (cached != null) return cached;
|
|
465
|
-
logger.debug("Fetching key {keyId} to verify signature...", { keyId });
|
|
466
|
-
let document;
|
|
1263
|
+
const start = performance.now();
|
|
1264
|
+
let outcome = { result: "error" };
|
|
467
1265
|
try {
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
1266
|
+
const logger = (0, _logtape_logtape.getLogger)([
|
|
1267
|
+
"fedify",
|
|
1268
|
+
"sig",
|
|
1269
|
+
"key"
|
|
1270
|
+
]);
|
|
1271
|
+
const keyId = cacheKey.href;
|
|
1272
|
+
const keyCache = options.keyCache;
|
|
1273
|
+
const cached = await getCachedFetchKey(cacheKey, keyId, cls, keyCache, logger);
|
|
1274
|
+
if (cached?.key === null && cached.cached) {
|
|
1275
|
+
const cachedUnavailable = await onCachedUnavailable(cacheKey, keyId, keyCache, logger);
|
|
1276
|
+
outcome = { result: "hit" };
|
|
1277
|
+
return cachedUnavailable;
|
|
1278
|
+
}
|
|
1279
|
+
if (cached != null) {
|
|
1280
|
+
outcome = { result: "hit" };
|
|
1281
|
+
return cached;
|
|
1282
|
+
}
|
|
1283
|
+
logger.debug("Fetching key {keyId} to verify signature...", { keyId });
|
|
1284
|
+
let document;
|
|
1285
|
+
try {
|
|
1286
|
+
document = (await (options.documentLoader ?? (0, _fedify_vocab_runtime.getDocumentLoader)())(keyId)).document;
|
|
1287
|
+
} catch (error) {
|
|
1288
|
+
const classified = classifyFetchError(error);
|
|
1289
|
+
const errored = await onFetchError(error, cacheKey, keyId, keyCache, logger);
|
|
1290
|
+
outcome = classified;
|
|
1291
|
+
return errored;
|
|
1292
|
+
}
|
|
1293
|
+
const resolved = await resolveFetchedKey(document, cacheKey, keyId, cls, options, logger);
|
|
1294
|
+
outcome = { result: resolved.key != null ? "fetched" : "invalid" };
|
|
1295
|
+
return resolved;
|
|
1296
|
+
} finally {
|
|
1297
|
+
recordKeyLookup(options.meterProvider, {
|
|
1298
|
+
durationMs: getDurationMs(start),
|
|
1299
|
+
result: outcome.result,
|
|
1300
|
+
remoteUrl: cacheKey,
|
|
1301
|
+
cacheEnabled: options.keyCache != null,
|
|
1302
|
+
statusCode: outcome.statusCode
|
|
1303
|
+
});
|
|
471
1304
|
}
|
|
472
|
-
return await resolveFetchedKey(document, cacheKey, keyId, cls, options, logger);
|
|
473
1305
|
}
|
|
474
1306
|
async function fetchKeyInternal(keyId, cls, options = {}) {
|
|
475
1307
|
return await fetchKeyWithResult(typeof keyId === "string" ? new URL(keyId) : keyId, cls, options, (_cacheKey, _keyId, _keyCache, _logger) => {
|
|
@@ -497,6 +1329,7 @@ async function fetchKeyInternal(keyId, cls, options = {}) {
|
|
|
497
1329
|
//#endregion
|
|
498
1330
|
//#region src/sig/http.ts
|
|
499
1331
|
const DEFAULT_MAX_REDIRECTION = 20;
|
|
1332
|
+
const DOUBLE_KNOCK_TRANSPORT_RETRY_DELAY_MS = 100;
|
|
500
1333
|
/**
|
|
501
1334
|
* Signs a request using the given private key.
|
|
502
1335
|
* @param request The request to sign.
|
|
@@ -799,6 +1632,27 @@ function parseKeyId(value) {
|
|
|
799
1632
|
function getKeyFetchErrorName(error) {
|
|
800
1633
|
return error.name || error.constructor.name || "Error";
|
|
801
1634
|
}
|
|
1635
|
+
/**
|
|
1636
|
+
* Known draft-cavage `algorithm` parameter values, used to keep the
|
|
1637
|
+
* `http_signatures.algorithm` metric attribute on a bounded set. The header
|
|
1638
|
+
* field is attacker-controlled and not used to select the verification
|
|
1639
|
+
* algorithm, so unknown values are dropped from the metric to prevent
|
|
1640
|
+
* cardinality blow-up.
|
|
1641
|
+
*/
|
|
1642
|
+
const DRAFT_KNOWN_ALGORITHMS = new Set([
|
|
1643
|
+
"ecdsa-sha256",
|
|
1644
|
+
"ecdsa-sha384",
|
|
1645
|
+
"ecdsa-sha512",
|
|
1646
|
+
"ed25519",
|
|
1647
|
+
"hs2019",
|
|
1648
|
+
"rsa-sha1",
|
|
1649
|
+
"rsa-sha256",
|
|
1650
|
+
"rsa-sha512"
|
|
1651
|
+
]);
|
|
1652
|
+
function classifyHttpVerifyResult(result) {
|
|
1653
|
+
if (result.verified) return "verified";
|
|
1654
|
+
return result.reason.type === "noSignature" ? "missing" : "rejected";
|
|
1655
|
+
}
|
|
802
1656
|
function recordVerificationResult(span, result) {
|
|
803
1657
|
span.setAttribute("http_signatures.verified", result.verified);
|
|
804
1658
|
if (result.verified === true) return;
|
|
@@ -842,27 +1696,37 @@ async function verifyRequestDetailed(request, options = {}) {
|
|
|
842
1696
|
span.setAttribute(_opentelemetry_semantic_conventions.ATTR_URL_FULL, request.url);
|
|
843
1697
|
for (const [name, value] of request.headers) span.setAttribute((0, _opentelemetry_semantic_conventions.ATTR_HTTP_REQUEST_HEADER)(name), value);
|
|
844
1698
|
}
|
|
1699
|
+
const start = performance.now();
|
|
1700
|
+
const metricsContext = {};
|
|
1701
|
+
let result;
|
|
1702
|
+
let threw = false;
|
|
845
1703
|
try {
|
|
846
1704
|
let spec = options.spec;
|
|
847
1705
|
if (spec == null) spec = request.headers.has("Signature-Input") ? "rfc9421" : "draft-cavage-http-signatures-12";
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
else result = await verifyRequestDraft(request, span, options);
|
|
1706
|
+
if (spec === "rfc9421") result = await verifyRequestRfc9421(request, span, metricsContext, options);
|
|
1707
|
+
else result = await verifyRequestDraft(request, span, metricsContext, options);
|
|
851
1708
|
recordVerificationResult(span, result);
|
|
852
1709
|
if (!result.verified) span.setStatus({ code: _opentelemetry_api.SpanStatusCode.ERROR });
|
|
853
1710
|
return result;
|
|
854
1711
|
} catch (error) {
|
|
1712
|
+
threw = true;
|
|
855
1713
|
span.setStatus({
|
|
856
1714
|
code: _opentelemetry_api.SpanStatusCode.ERROR,
|
|
857
1715
|
message: String(error)
|
|
858
1716
|
});
|
|
859
1717
|
throw error;
|
|
860
1718
|
} finally {
|
|
1719
|
+
const classified = threw ? "error" : classifyHttpVerifyResult(result);
|
|
1720
|
+
const failureReason = result != null && !result.verified && result.reason.type !== "noSignature" ? result.reason.type : void 0;
|
|
1721
|
+
getFederationMetrics(options.meterProvider).recordSignatureVerificationDuration(getDurationMs(start), "http", classified, {
|
|
1722
|
+
algorithm: metricsContext.algorithm,
|
|
1723
|
+
failureReason
|
|
1724
|
+
});
|
|
861
1725
|
span.end();
|
|
862
1726
|
}
|
|
863
1727
|
});
|
|
864
1728
|
}
|
|
865
|
-
async function verifyRequestDraft(request, span, { documentLoader, contextLoader, timeWindow, currentTime, keyCache, tracerProvider } = {}) {
|
|
1729
|
+
async function verifyRequestDraft(request, span, metricsContext, { documentLoader, contextLoader, timeWindow, currentTime, keyCache, meterProvider, tracerProvider } = {}) {
|
|
866
1730
|
const logger = (0, _logtape_logtape.getLogger)([
|
|
867
1731
|
"fedify",
|
|
868
1732
|
"sig",
|
|
@@ -1010,13 +1874,18 @@ async function verifyRequestDraft(request, span, { documentLoader, contextLoader
|
|
|
1010
1874
|
const keyIdUrl = parseKeyId(keyId);
|
|
1011
1875
|
if (keyIdUrl == null) return invalidSignatureResult(null);
|
|
1012
1876
|
span?.setAttribute("http_signatures.key_id", keyId);
|
|
1013
|
-
if ("algorithm" in sigValues)
|
|
1014
|
-
|
|
1877
|
+
if ("algorithm" in sigValues) {
|
|
1878
|
+
span?.setAttribute("http_signatures.algorithm", sigValues.algorithm);
|
|
1879
|
+
const normalizedAlgorithm = sigValues.algorithm.toLowerCase();
|
|
1880
|
+
if (DRAFT_KNOWN_ALGORITHMS.has(normalizedAlgorithm)) metricsContext.algorithm = normalizedAlgorithm;
|
|
1881
|
+
}
|
|
1882
|
+
const { key, cached, fetchError } = await measureSignatureKeyFetch(meterProvider, "http", () => fetchKeyDetailed(keyIdUrl, _fedify_vocab.CryptographicKey, {
|
|
1015
1883
|
documentLoader,
|
|
1016
1884
|
contextLoader,
|
|
1017
1885
|
keyCache,
|
|
1018
|
-
tracerProvider
|
|
1019
|
-
|
|
1886
|
+
tracerProvider,
|
|
1887
|
+
meterProvider
|
|
1888
|
+
}));
|
|
1020
1889
|
if (fetchError != null) return keyFetchErrorResult(keyIdUrl, fetchError);
|
|
1021
1890
|
if (key == null) return invalidSignatureResult(keyIdUrl);
|
|
1022
1891
|
const headerNames = headers.split(/\s+/g);
|
|
@@ -1038,7 +1907,7 @@ async function verifyRequestDraft(request, span, { documentLoader, contextLoader
|
|
|
1038
1907
|
signature,
|
|
1039
1908
|
message
|
|
1040
1909
|
});
|
|
1041
|
-
return await
|
|
1910
|
+
return await verifyRequestDraft(originalRequest, span, metricsContext, {
|
|
1042
1911
|
documentLoader,
|
|
1043
1912
|
contextLoader,
|
|
1044
1913
|
timeWindow,
|
|
@@ -1046,7 +1915,9 @@ async function verifyRequestDraft(request, span, { documentLoader, contextLoader
|
|
|
1046
1915
|
keyCache: {
|
|
1047
1916
|
get: () => Promise.resolve(void 0),
|
|
1048
1917
|
set: async (keyId, key) => await keyCache?.set(keyId, key)
|
|
1049
|
-
}
|
|
1918
|
+
},
|
|
1919
|
+
meterProvider,
|
|
1920
|
+
tracerProvider
|
|
1050
1921
|
});
|
|
1051
1922
|
}
|
|
1052
1923
|
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}", {
|
|
@@ -1122,7 +1993,7 @@ async function verifyRfc9421ContentDigest(digestHeader, body) {
|
|
|
1122
1993
|
}
|
|
1123
1994
|
return false;
|
|
1124
1995
|
}
|
|
1125
|
-
async function verifyRequestRfc9421(request, span, { documentLoader, contextLoader, timeWindow, currentTime, keyCache, tracerProvider } = {}) {
|
|
1996
|
+
async function verifyRequestRfc9421(request, span, metricsContext, { documentLoader, contextLoader, timeWindow, currentTime, keyCache, meterProvider, tracerProvider } = {}) {
|
|
1126
1997
|
const logger = (0, _logtape_logtape.getLogger)([
|
|
1127
1998
|
"fedify",
|
|
1128
1999
|
"sig",
|
|
@@ -1156,9 +2027,14 @@ async function verifyRequestRfc9421(request, span, { documentLoader, contextLoad
|
|
|
1156
2027
|
return invalidSignatureResult(null);
|
|
1157
2028
|
}
|
|
1158
2029
|
let failure = noSignatureResult();
|
|
2030
|
+
let failureAlgorithm;
|
|
2031
|
+
const setFailure = (result, algorithm) => {
|
|
2032
|
+
failure = result;
|
|
2033
|
+
failureAlgorithm = algorithm;
|
|
2034
|
+
};
|
|
1159
2035
|
for (const sigName of signatureNames) {
|
|
1160
2036
|
if (!signatures[sigName]) {
|
|
1161
|
-
|
|
2037
|
+
setFailure(invalidSignatureResult(parseKeyId(signatureInputs[sigName]?.keyId)));
|
|
1162
2038
|
continue;
|
|
1163
2039
|
}
|
|
1164
2040
|
const sigInput = signatureInputs[sigName];
|
|
@@ -1169,7 +2045,7 @@ async function verifyRequestRfc9421(request, span, { documentLoader, contextLoad
|
|
|
1169
2045
|
signatureName: sigName,
|
|
1170
2046
|
signatureInput: signatureInputHeader
|
|
1171
2047
|
});
|
|
1172
|
-
|
|
2048
|
+
setFailure(invalidSignatureResult(null));
|
|
1173
2049
|
continue;
|
|
1174
2050
|
}
|
|
1175
2051
|
if (!sigInput.created) {
|
|
@@ -1177,7 +2053,7 @@ async function verifyRequestRfc9421(request, span, { documentLoader, contextLoad
|
|
|
1177
2053
|
signatureName: sigName,
|
|
1178
2054
|
signatureInput: signatureInputHeader
|
|
1179
2055
|
});
|
|
1180
|
-
|
|
2056
|
+
setFailure(invalidSignatureResult(keyId));
|
|
1181
2057
|
continue;
|
|
1182
2058
|
}
|
|
1183
2059
|
const signatureCreated = Temporal.Instant.fromEpochMilliseconds(sigInput.created * 1e3);
|
|
@@ -1189,14 +2065,14 @@ async function verifyRequestRfc9421(request, span, { documentLoader, contextLoad
|
|
|
1189
2065
|
created: signatureCreated.toString(),
|
|
1190
2066
|
now: now.toString()
|
|
1191
2067
|
});
|
|
1192
|
-
|
|
2068
|
+
setFailure(invalidSignatureResult(keyId));
|
|
1193
2069
|
continue;
|
|
1194
2070
|
} else if (Temporal.Instant.compare(signatureCreated, now.subtract(tw)) < 0) {
|
|
1195
2071
|
logger.debug("Failed to verify; signature created time is too far in the past.", {
|
|
1196
2072
|
created: signatureCreated.toString(),
|
|
1197
2073
|
now: now.toString()
|
|
1198
2074
|
});
|
|
1199
|
-
|
|
2075
|
+
setFailure(invalidSignatureResult(keyId));
|
|
1200
2076
|
continue;
|
|
1201
2077
|
}
|
|
1202
2078
|
}
|
|
@@ -1204,34 +2080,35 @@ async function verifyRequestRfc9421(request, span, { documentLoader, contextLoad
|
|
|
1204
2080
|
const contentDigestHeader = request.headers.get("Content-Digest");
|
|
1205
2081
|
if (!contentDigestHeader) {
|
|
1206
2082
|
logger.debug("Failed to verify; Content-Digest header required but not found.", { components: sigInput.components });
|
|
1207
|
-
|
|
2083
|
+
setFailure(invalidSignatureResult(keyId));
|
|
1208
2084
|
continue;
|
|
1209
2085
|
}
|
|
1210
2086
|
if (!await verifyRfc9421ContentDigest(contentDigestHeader, await request.arrayBuffer())) {
|
|
1211
2087
|
logger.debug("Failed to verify; Content-Digest verification failed.", { contentDigest: contentDigestHeader });
|
|
1212
|
-
|
|
2088
|
+
setFailure(invalidSignatureResult(keyId));
|
|
1213
2089
|
continue;
|
|
1214
2090
|
}
|
|
1215
2091
|
}
|
|
1216
2092
|
span?.setAttribute("http_signatures.key_id", sigInput.keyId);
|
|
1217
2093
|
span?.setAttribute("http_signatures.created", sigInput.created.toString());
|
|
1218
2094
|
if (keyId == null) {
|
|
1219
|
-
|
|
2095
|
+
setFailure(invalidSignatureResult(null));
|
|
1220
2096
|
continue;
|
|
1221
2097
|
}
|
|
1222
|
-
const { key, cached, fetchError } = await fetchKeyDetailed(keyId, _fedify_vocab.CryptographicKey, {
|
|
2098
|
+
const { key, cached, fetchError } = await measureSignatureKeyFetch(meterProvider, "http", () => fetchKeyDetailed(keyId, _fedify_vocab.CryptographicKey, {
|
|
1223
2099
|
documentLoader,
|
|
1224
2100
|
contextLoader,
|
|
1225
2101
|
keyCache,
|
|
1226
|
-
tracerProvider
|
|
1227
|
-
|
|
2102
|
+
tracerProvider,
|
|
2103
|
+
meterProvider
|
|
2104
|
+
}));
|
|
1228
2105
|
if (fetchError != null) {
|
|
1229
|
-
|
|
2106
|
+
setFailure(keyFetchErrorResult(keyId, fetchError));
|
|
1230
2107
|
continue;
|
|
1231
2108
|
}
|
|
1232
2109
|
if (!key) {
|
|
1233
2110
|
logger.debug("Failed to fetch key: {keyId}", { keyId: sigInput.keyId });
|
|
1234
|
-
|
|
2111
|
+
setFailure(invalidSignatureResult(keyId));
|
|
1235
2112
|
continue;
|
|
1236
2113
|
}
|
|
1237
2114
|
let alg = sigInput.alg?.toLowerCase();
|
|
@@ -1243,12 +2120,13 @@ async function verifyRequestRfc9421(request, span, { documentLoader, contextLoad
|
|
|
1243
2120
|
}
|
|
1244
2121
|
if (alg) span?.setAttribute("http_signatures.algorithm", alg);
|
|
1245
2122
|
const algorithm = alg && rfc9421AlgorithmMap[alg];
|
|
2123
|
+
const candidateAlgorithm = algorithm ? alg : void 0;
|
|
1246
2124
|
if (!algorithm) {
|
|
1247
2125
|
logger.debug("Failed to verify; unsupported algorithm: {algorithm}", {
|
|
1248
2126
|
algorithm: sigInput.alg,
|
|
1249
2127
|
supported: Object.keys(rfc9421AlgorithmMap)
|
|
1250
2128
|
});
|
|
1251
|
-
|
|
2129
|
+
setFailure(invalidSignatureResult(keyId));
|
|
1252
2130
|
continue;
|
|
1253
2131
|
}
|
|
1254
2132
|
let signatureBase;
|
|
@@ -1259,20 +2137,22 @@ async function verifyRequestRfc9421(request, span, { documentLoader, contextLoad
|
|
|
1259
2137
|
error,
|
|
1260
2138
|
signatureInput: sigInput
|
|
1261
2139
|
});
|
|
1262
|
-
|
|
2140
|
+
setFailure(invalidSignatureResult(keyId), candidateAlgorithm);
|
|
1263
2141
|
continue;
|
|
1264
2142
|
}
|
|
1265
2143
|
const signatureBaseBytes = new TextEncoder().encode(signatureBase);
|
|
1266
2144
|
span?.setAttribute("http_signatures.signature", (0, byte_encodings_hex.encodeHex)(sigBytes));
|
|
1267
2145
|
try {
|
|
1268
|
-
if (await crypto.subtle.verify(algorithm, key.publicKey, sigBytes.slice(), signatureBaseBytes))
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
2146
|
+
if (await crypto.subtle.verify(algorithm, key.publicKey, sigBytes.slice(), signatureBaseBytes)) {
|
|
2147
|
+
metricsContext.algorithm = candidateAlgorithm;
|
|
2148
|
+
return {
|
|
2149
|
+
verified: true,
|
|
2150
|
+
key,
|
|
2151
|
+
signatureLabel: sigName
|
|
2152
|
+
};
|
|
2153
|
+
} else if (cached) {
|
|
1274
2154
|
logger.debug("Failed to verify with cached key {keyId}; retrying with fresh key...", { keyId: sigInput.keyId });
|
|
1275
|
-
return await
|
|
2155
|
+
return await verifyRequestRfc9421(originalRequest, span, metricsContext, {
|
|
1276
2156
|
documentLoader,
|
|
1277
2157
|
contextLoader,
|
|
1278
2158
|
timeWindow,
|
|
@@ -1281,14 +2161,16 @@ async function verifyRequestRfc9421(request, span, { documentLoader, contextLoad
|
|
|
1281
2161
|
get: () => Promise.resolve(void 0),
|
|
1282
2162
|
set: async (keyId, key) => await keyCache?.set(keyId, key)
|
|
1283
2163
|
},
|
|
1284
|
-
spec: "rfc9421"
|
|
2164
|
+
spec: "rfc9421",
|
|
2165
|
+
meterProvider,
|
|
2166
|
+
tracerProvider
|
|
1285
2167
|
});
|
|
1286
2168
|
} else {
|
|
1287
2169
|
logger.debug("Failed to verify signature with fetched key {keyId}; signature invalid.", {
|
|
1288
2170
|
keyId: sigInput.keyId,
|
|
1289
2171
|
signatureBase
|
|
1290
2172
|
});
|
|
1291
|
-
|
|
2173
|
+
setFailure(invalidSignatureResult(keyId), candidateAlgorithm);
|
|
1292
2174
|
}
|
|
1293
2175
|
} catch (error) {
|
|
1294
2176
|
logger.debug("Error during signature verification: {error}", {
|
|
@@ -1296,9 +2178,10 @@ async function verifyRequestRfc9421(request, span, { documentLoader, contextLoad
|
|
|
1296
2178
|
keyId: sigInput.keyId,
|
|
1297
2179
|
algorithm: sigInput.alg
|
|
1298
2180
|
});
|
|
1299
|
-
|
|
2181
|
+
setFailure(invalidSignatureResult(keyId), candidateAlgorithm);
|
|
1300
2182
|
}
|
|
1301
2183
|
}
|
|
2184
|
+
metricsContext.algorithm = failureAlgorithm;
|
|
1302
2185
|
return failure;
|
|
1303
2186
|
}
|
|
1304
2187
|
/**
|
|
@@ -1325,6 +2208,59 @@ function createRedirectRequest(request, location, body) {
|
|
|
1325
2208
|
cache: request.cache
|
|
1326
2209
|
});
|
|
1327
2210
|
}
|
|
2211
|
+
async function fetchDoubleKnockRequest(request, signedRequest, signal) {
|
|
2212
|
+
const maxAttempts = request.method === "GET" || request.method === "HEAD" ? 2 : 1;
|
|
2213
|
+
for (let attempt = 1;; attempt++) try {
|
|
2214
|
+
return await fetch(signedRequest, {
|
|
2215
|
+
redirect: "manual",
|
|
2216
|
+
signal
|
|
2217
|
+
});
|
|
2218
|
+
} catch (error) {
|
|
2219
|
+
const abortedSignal = getAbortedSignal(signal, request.signal, signedRequest.signal);
|
|
2220
|
+
if (abortedSignal != null) throw getAbortReason(abortedSignal);
|
|
2221
|
+
if (isAbortError(error)) throw error;
|
|
2222
|
+
if (attempt >= maxAttempts) throw createFetchError(request.url, error);
|
|
2223
|
+
await sleep(DOUBLE_KNOCK_TRANSPORT_RETRY_DELAY_MS, signal, request.signal, signedRequest.signal);
|
|
2224
|
+
}
|
|
2225
|
+
}
|
|
2226
|
+
function createFetchError(url, cause) {
|
|
2227
|
+
const error = new _fedify_vocab_runtime.FetchError(url, cause instanceof Error ? cause.message : String(cause));
|
|
2228
|
+
error.cause = cause;
|
|
2229
|
+
return error;
|
|
2230
|
+
}
|
|
2231
|
+
function isAbortError(error) {
|
|
2232
|
+
return error instanceof Error && error.name === "AbortError";
|
|
2233
|
+
}
|
|
2234
|
+
async function sleep(ms, ...signals) {
|
|
2235
|
+
const abortSignals = signals.filter((signal) => signal != null);
|
|
2236
|
+
const abortedSignal = getAbortedSignal(...abortSignals);
|
|
2237
|
+
if (abortedSignal != null) throw getAbortReason(abortedSignal);
|
|
2238
|
+
if (abortSignals.length < 1) {
|
|
2239
|
+
await new Promise((resolve) => setTimeout(resolve, ms));
|
|
2240
|
+
return;
|
|
2241
|
+
}
|
|
2242
|
+
await new Promise((resolve, reject) => {
|
|
2243
|
+
const removeAbortListeners = () => {
|
|
2244
|
+
for (const signal of abortSignals) signal.removeEventListener("abort", handleAbort);
|
|
2245
|
+
};
|
|
2246
|
+
const timeout = setTimeout(() => {
|
|
2247
|
+
removeAbortListeners();
|
|
2248
|
+
resolve();
|
|
2249
|
+
}, ms);
|
|
2250
|
+
function handleAbort(event) {
|
|
2251
|
+
clearTimeout(timeout);
|
|
2252
|
+
removeAbortListeners();
|
|
2253
|
+
reject(getAbortReason(event.currentTarget));
|
|
2254
|
+
}
|
|
2255
|
+
for (const signal of abortSignals) signal.addEventListener("abort", handleAbort, { once: true });
|
|
2256
|
+
});
|
|
2257
|
+
}
|
|
2258
|
+
function getAbortedSignal(...signals) {
|
|
2259
|
+
return signals.find((signal) => signal?.aborted);
|
|
2260
|
+
}
|
|
2261
|
+
function getAbortReason(signal) {
|
|
2262
|
+
return signal.reason ?? new DOMException("The operation was aborted.", "AbortError");
|
|
2263
|
+
}
|
|
1328
2264
|
/**
|
|
1329
2265
|
* Performs a double-knock request to the given URL. For the details of
|
|
1330
2266
|
* double-knocking, see
|
|
@@ -1351,10 +2287,7 @@ async function doubleKnockInternal(request, identity, options, redirected = 0, v
|
|
|
1351
2287
|
body
|
|
1352
2288
|
});
|
|
1353
2289
|
log?.(signedRequest);
|
|
1354
|
-
let response = await
|
|
1355
|
-
redirect: "manual",
|
|
1356
|
-
signal
|
|
1357
|
-
});
|
|
2290
|
+
let response = await fetchDoubleKnockRequest(request, signedRequest, signal);
|
|
1358
2291
|
if (response.status >= 300 && response.status < 400 && response.headers.has("Location")) {
|
|
1359
2292
|
if (redirected >= maximumRedirection) throw new _fedify_vocab_runtime.FetchError(request.url, `Too many redirections (${redirected + 1})`);
|
|
1360
2293
|
const redirectRequest = createRedirectRequest(request, response.headers.get("Location"), body);
|
|
@@ -1429,10 +2362,7 @@ async function doubleKnockInternal(request, identity, options, redirected = 0, v
|
|
|
1429
2362
|
body
|
|
1430
2363
|
});
|
|
1431
2364
|
log?.(signedRequest);
|
|
1432
|
-
response = await
|
|
1433
|
-
redirect: "manual",
|
|
1434
|
-
signal
|
|
1435
|
-
});
|
|
2365
|
+
response = await fetchDoubleKnockRequest(request, signedRequest, signal);
|
|
1436
2366
|
if (response.status >= 300 && response.status < 400 && response.headers.has("Location")) {
|
|
1437
2367
|
if (redirected >= maximumRedirection) throw new _fedify_vocab_runtime.FetchError(request.url, `Too many redirections (${redirected + 1})`);
|
|
1438
2368
|
const redirectRequest = createRedirectRequest(request, response.headers.get("Location"), body);
|
|
@@ -1515,12 +2445,48 @@ Object.defineProperty(exports, "generateCryptoKeyPair", {
|
|
|
1515
2445
|
return generateCryptoKeyPair;
|
|
1516
2446
|
}
|
|
1517
2447
|
});
|
|
2448
|
+
Object.defineProperty(exports, "getDurationMs", {
|
|
2449
|
+
enumerable: true,
|
|
2450
|
+
get: function() {
|
|
2451
|
+
return getDurationMs;
|
|
2452
|
+
}
|
|
2453
|
+
});
|
|
2454
|
+
Object.defineProperty(exports, "getFederationMetrics", {
|
|
2455
|
+
enumerable: true,
|
|
2456
|
+
get: function() {
|
|
2457
|
+
return getFederationMetrics;
|
|
2458
|
+
}
|
|
2459
|
+
});
|
|
2460
|
+
Object.defineProperty(exports, "getRemoteHost", {
|
|
2461
|
+
enumerable: true,
|
|
2462
|
+
get: function() {
|
|
2463
|
+
return getRemoteHost;
|
|
2464
|
+
}
|
|
2465
|
+
});
|
|
1518
2466
|
Object.defineProperty(exports, "importJwk", {
|
|
1519
2467
|
enumerable: true,
|
|
1520
2468
|
get: function() {
|
|
1521
2469
|
return importJwk;
|
|
1522
2470
|
}
|
|
1523
2471
|
});
|
|
2472
|
+
Object.defineProperty(exports, "instrumentDocumentLoader", {
|
|
2473
|
+
enumerable: true,
|
|
2474
|
+
get: function() {
|
|
2475
|
+
return instrumentDocumentLoader;
|
|
2476
|
+
}
|
|
2477
|
+
});
|
|
2478
|
+
Object.defineProperty(exports, "isAbortError", {
|
|
2479
|
+
enumerable: true,
|
|
2480
|
+
get: function() {
|
|
2481
|
+
return isAbortError$1;
|
|
2482
|
+
}
|
|
2483
|
+
});
|
|
2484
|
+
Object.defineProperty(exports, "measureSignatureKeyFetch", {
|
|
2485
|
+
enumerable: true,
|
|
2486
|
+
get: function() {
|
|
2487
|
+
return measureSignatureKeyFetch;
|
|
2488
|
+
}
|
|
2489
|
+
});
|
|
1524
2490
|
Object.defineProperty(exports, "name", {
|
|
1525
2491
|
enumerable: true,
|
|
1526
2492
|
get: function() {
|
|
@@ -1539,6 +2505,78 @@ Object.defineProperty(exports, "parseRfc9421SignatureInput", {
|
|
|
1539
2505
|
return parseRfc9421SignatureInput;
|
|
1540
2506
|
}
|
|
1541
2507
|
});
|
|
2508
|
+
Object.defineProperty(exports, "recordCircuitBreakerStateChange", {
|
|
2509
|
+
enumerable: true,
|
|
2510
|
+
get: function() {
|
|
2511
|
+
return recordCircuitBreakerStateChange;
|
|
2512
|
+
}
|
|
2513
|
+
});
|
|
2514
|
+
Object.defineProperty(exports, "recordCollectionDispatchDuration", {
|
|
2515
|
+
enumerable: true,
|
|
2516
|
+
get: function() {
|
|
2517
|
+
return recordCollectionDispatchDuration;
|
|
2518
|
+
}
|
|
2519
|
+
});
|
|
2520
|
+
Object.defineProperty(exports, "recordCollectionPageItems", {
|
|
2521
|
+
enumerable: true,
|
|
2522
|
+
get: function() {
|
|
2523
|
+
return recordCollectionPageItems;
|
|
2524
|
+
}
|
|
2525
|
+
});
|
|
2526
|
+
Object.defineProperty(exports, "recordCollectionRequest", {
|
|
2527
|
+
enumerable: true,
|
|
2528
|
+
get: function() {
|
|
2529
|
+
return recordCollectionRequest;
|
|
2530
|
+
}
|
|
2531
|
+
});
|
|
2532
|
+
Object.defineProperty(exports, "recordCollectionTotalItems", {
|
|
2533
|
+
enumerable: true,
|
|
2534
|
+
get: function() {
|
|
2535
|
+
return recordCollectionTotalItems;
|
|
2536
|
+
}
|
|
2537
|
+
});
|
|
2538
|
+
Object.defineProperty(exports, "recordDocumentCache", {
|
|
2539
|
+
enumerable: true,
|
|
2540
|
+
get: function() {
|
|
2541
|
+
return recordDocumentCache;
|
|
2542
|
+
}
|
|
2543
|
+
});
|
|
2544
|
+
Object.defineProperty(exports, "recordFanoutRecipients", {
|
|
2545
|
+
enumerable: true,
|
|
2546
|
+
get: function() {
|
|
2547
|
+
return recordFanoutRecipients;
|
|
2548
|
+
}
|
|
2549
|
+
});
|
|
2550
|
+
Object.defineProperty(exports, "recordInboxActivity", {
|
|
2551
|
+
enumerable: true,
|
|
2552
|
+
get: function() {
|
|
2553
|
+
return recordInboxActivity;
|
|
2554
|
+
}
|
|
2555
|
+
});
|
|
2556
|
+
Object.defineProperty(exports, "recordOutboxActivity", {
|
|
2557
|
+
enumerable: true,
|
|
2558
|
+
get: function() {
|
|
2559
|
+
return recordOutboxActivity;
|
|
2560
|
+
}
|
|
2561
|
+
});
|
|
2562
|
+
Object.defineProperty(exports, "recordOutboxEnqueue", {
|
|
2563
|
+
enumerable: true,
|
|
2564
|
+
get: function() {
|
|
2565
|
+
return recordOutboxEnqueue;
|
|
2566
|
+
}
|
|
2567
|
+
});
|
|
2568
|
+
Object.defineProperty(exports, "recordWebFingerHandle", {
|
|
2569
|
+
enumerable: true,
|
|
2570
|
+
get: function() {
|
|
2571
|
+
return recordWebFingerHandle;
|
|
2572
|
+
}
|
|
2573
|
+
});
|
|
2574
|
+
Object.defineProperty(exports, "registerQueueDepthGauge", {
|
|
2575
|
+
enumerable: true,
|
|
2576
|
+
get: function() {
|
|
2577
|
+
return registerQueueDepthGauge;
|
|
2578
|
+
}
|
|
2579
|
+
});
|
|
1542
2580
|
Object.defineProperty(exports, "signRequest", {
|
|
1543
2581
|
enumerable: true,
|
|
1544
2582
|
get: function() {
|