@fedify/fedify 2.3.0-dev.1137 → 2.3.0-dev.1145

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 (52) hide show
  1. package/dist/{builder-BCkBXxky.mjs → builder-ShiR1K6b.mjs} +2 -2
  2. package/dist/compat/transformers.test.mjs +1 -1
  3. package/dist/{deno-B_9yJW3w.mjs → deno-h0TWFuEz.mjs} +1 -1
  4. package/dist/{docloader-BT89tyFr.mjs → docloader-BdDN0Aqx.mjs} +2 -2
  5. package/dist/federation/builder.test.mjs +1 -1
  6. package/dist/federation/handler.test.mjs +2 -2
  7. package/dist/federation/idempotency.test.mjs +2 -2
  8. package/dist/federation/metrics.test.mjs +60 -1
  9. package/dist/federation/middleware.test.mjs +6 -6
  10. package/dist/federation/mod.cjs +1 -1
  11. package/dist/federation/mod.d.cts +1 -1
  12. package/dist/federation/mod.d.ts +1 -1
  13. package/dist/federation/mod.js +1 -1
  14. package/dist/federation/send.test.mjs +3 -3
  15. package/dist/federation/webfinger.test.mjs +147 -2
  16. package/dist/{http-CWoeyogl.cjs → http-7kAB7PVx.cjs} +53 -1
  17. package/dist/{http-CToqG5ap.js → http-B2hxA7dO.js} +48 -2
  18. package/dist/{http-Cyx5SNuu.mjs → http-QzW9IWfs.mjs} +3 -3
  19. package/dist/{key-CkkMJBjF.mjs → key-Dh2OK1XQ.mjs} +2 -2
  20. package/dist/{kv-cache-DuEwFYcN.cjs → kv-cache-DCPp-MT0.cjs} +1 -1
  21. package/dist/{kv-cache-VHFP42vY.mjs → kv-cache-EZRIPZXD.mjs} +1 -1
  22. package/dist/{kv-cache-CuCn2xvM.js → kv-cache-b22dNkjt.js} +1 -1
  23. package/dist/{ld-k8yqD2a-.mjs → ld-eZbar1rr.mjs} +3 -3
  24. package/dist/{metrics-iRBg8jTk.mjs → metrics-E0hAHtLZ.mjs} +48 -2
  25. package/dist/{middleware-BWLUrbS9.cjs → middleware-BUl1BH4x.cjs} +71 -19
  26. package/dist/{middleware-D7FrhN9q.js → middleware-BrGIM_Ra.js} +71 -19
  27. package/dist/{middleware-DQEgdr83.mjs → middleware-CyJDCmNg.mjs} +79 -27
  28. package/dist/{middleware-CztxpARM.mjs → middleware-mToCR2tG.mjs} +1 -1
  29. package/dist/{mod-C504qevA.d.cts → mod-CI9fduEi.d.cts} +10 -1
  30. package/dist/{mod-wYfuXeDE.d.ts → mod-CkRiJHGA.d.ts} +10 -1
  31. package/dist/mod.cjs +4 -4
  32. package/dist/mod.d.cts +1 -1
  33. package/dist/mod.d.ts +1 -1
  34. package/dist/mod.js +4 -4
  35. package/dist/nodeinfo/handler.test.mjs +1 -1
  36. package/dist/{owner-nmXdvXpc.mjs → owner-ByO_Fw6U.mjs} +2 -2
  37. package/dist/{proof-NRmtrTDu.js → proof-BkRyFchv.js} +1 -1
  38. package/dist/{proof-DpwO1T4S.mjs → proof-CSo0S8OK.mjs} +3 -3
  39. package/dist/{proof-CcsIJLTn.cjs → proof-jVqClF49.cjs} +1 -1
  40. package/dist/{send-DvX2tYyZ.mjs → send-jzrTV1FU.mjs} +3 -3
  41. package/dist/sig/http.test.mjs +2 -2
  42. package/dist/sig/key.test.mjs +1 -1
  43. package/dist/sig/ld.test.mjs +2 -2
  44. package/dist/sig/mod.cjs +2 -2
  45. package/dist/sig/mod.js +2 -2
  46. package/dist/sig/owner.test.mjs +1 -1
  47. package/dist/sig/proof.test.mjs +1 -1
  48. package/dist/utils/docloader.test.mjs +2 -2
  49. package/dist/utils/kv-cache.test.mjs +1 -1
  50. package/dist/utils/mod.cjs +1 -1
  51. package/dist/utils/mod.js +1 -1
  52. package/package.json +7 -7
@@ -1,7 +1,7 @@
1
1
  import { Temporal } from "@js-temporal/polyfill";
2
2
  import { URLPattern } from "urlpattern-polyfill";
3
3
  globalThis.addEventListener = () => {};
4
- import { c as recordDocumentCache } from "./metrics-iRBg8jTk.mjs";
4
+ import { c as recordDocumentCache } from "./metrics-E0hAHtLZ.mjs";
5
5
  import { getLogger } from "@logtape/logtape";
6
6
  import { preloadedContexts } from "@fedify/vocab-runtime";
7
7
  //#region src/utils/kv-cache.ts
@@ -1,6 +1,6 @@
1
1
  import { Temporal } from "@js-temporal/polyfill";
2
2
  import { URLPattern } from "urlpattern-polyfill";
3
- import { d as validateCryptoKey, t as doubleKnock, v as recordDocumentCache } from "./http-CToqG5ap.js";
3
+ import { d as validateCryptoKey, t as doubleKnock, v as recordDocumentCache } from "./http-B2hxA7dO.js";
4
4
  import { getLogger } from "@logtape/logtape";
5
5
  import { curry } from "es-toolkit";
6
6
  import { UrlError, createActivityPubRequest, getRemoteDocument, logRequest, preloadedContexts, validatePublicUrl } from "@fedify/vocab-runtime";
@@ -1,9 +1,9 @@
1
1
  import "@js-temporal/polyfill";
2
2
  import "urlpattern-polyfill";
3
3
  globalThis.addEventListener = () => {};
4
- import { n as version, t as name } from "./deno-B_9yJW3w.mjs";
5
- import { n as getDurationMs, r as getFederationMetrics, s as measureSignatureKeyFetch } from "./metrics-iRBg8jTk.mjs";
6
- import { n as fetchKey, o as validateCryptoKey } from "./key-CkkMJBjF.mjs";
4
+ import { n as version, t as name } from "./deno-h0TWFuEz.mjs";
5
+ import { n as getDurationMs, r as getFederationMetrics, s as measureSignatureKeyFetch } from "./metrics-E0hAHtLZ.mjs";
6
+ import { n as fetchKey, o as validateCryptoKey } from "./key-Dh2OK1XQ.mjs";
7
7
  import { getLogger } from "@logtape/logtape";
8
8
  import { Activity, CryptographicKey, Object as Object$1, getTypeId } from "@fedify/vocab";
9
9
  import { SpanStatusCode, trace } from "@opentelemetry/api";
@@ -1,7 +1,7 @@
1
1
  import "@js-temporal/polyfill";
2
2
  import "urlpattern-polyfill";
3
3
  globalThis.addEventListener = () => {};
4
- import { n as version, t as name } from "./deno-B_9yJW3w.mjs";
4
+ import { n as version, t as name } from "./deno-h0TWFuEz.mjs";
5
5
  import { metrics } from "@opentelemetry/api";
6
6
  import { FetchError } from "@fedify/vocab-runtime";
7
7
  //#region src/federation/metrics.ts
@@ -29,6 +29,8 @@ var FederationMetrics = class {
29
29
  documentFetch;
30
30
  documentFetchDuration;
31
31
  documentCache;
32
+ webFingerHandle;
33
+ webFingerHandleDuration;
32
34
  constructor(meterProvider) {
33
35
  const meter = meterProvider.getMeter(name, version);
34
36
  this.deliverySent = meter.createCounter("activitypub.delivery.sent", {
@@ -187,6 +189,30 @@ var FederationMetrics = class {
187
189
  description: "KV-backed document loader cache lookups, with `hit` or `miss` classification.",
188
190
  unit: "{lookup}"
189
191
  });
192
+ this.webFingerHandle = meter.createCounter("webfinger.handle", {
193
+ description: "Incoming WebFinger requests handled by Fedify, classified by terminal outcome.",
194
+ unit: "{request}"
195
+ });
196
+ this.webFingerHandleDuration = meter.createHistogram("webfinger.handle.duration", {
197
+ description: "Duration of incoming WebFinger request handling in Fedify.",
198
+ unit: "ms",
199
+ advice: { explicitBucketBoundaries: [
200
+ 5,
201
+ 10,
202
+ 25,
203
+ 50,
204
+ 75,
205
+ 100,
206
+ 250,
207
+ 500,
208
+ 750,
209
+ 1e3,
210
+ 2500,
211
+ 5e3,
212
+ 7500,
213
+ 1e4
214
+ ] }
215
+ });
190
216
  }
191
217
  recordDelivery(inbox, durationMs, success, activityType) {
192
218
  const deliveryAttributes = {
@@ -300,6 +326,13 @@ var FederationMetrics = class {
300
326
  if (attrs.remoteUrl != null) attributes["activitypub.remote.host"] = getRemoteHost(attrs.remoteUrl);
301
327
  this.documentCache.add(1, attributes);
302
328
  }
329
+ recordWebFingerHandle(attrs) {
330
+ const attributes = { "webfinger.handle.result": attrs.result };
331
+ if (attrs.scheme != null) attributes["webfinger.resource.scheme"] = attrs.scheme;
332
+ if (attrs.statusCode != null) attributes["http.response.status_code"] = attrs.statusCode;
333
+ this.webFingerHandle.add(1, attributes);
334
+ this.webFingerHandleDuration.record(attrs.durationMs, attributes);
335
+ }
303
336
  };
304
337
  function buildActivityLifecycleAttributes(result, activityType) {
305
338
  const attributes = { "activitypub.processing.result": result };
@@ -424,6 +457,19 @@ function recordDocumentCache(meterProvider, attrs) {
424
457
  getFederationMetrics(meterProvider).recordDocumentCache(attrs);
425
458
  }
426
459
  /**
460
+ * Records one measurement on `webfinger.handle` (counter) and
461
+ * `webfinger.handle.duration` (histogram) for an incoming WebFinger
462
+ * request handled by Fedify. Counter and histogram are always recorded
463
+ * together, with `webfinger.handle.result` set to one of `resolved`,
464
+ * `invalid`, `not_found`, `tombstoned`, or `error`. The queried
465
+ * resource string is deliberately excluded; it remains on the
466
+ * `webfinger.handle` span for trace-level investigation.
467
+ * @since 2.3.0
468
+ */
469
+ function recordWebFingerHandle(meterProvider, attrs) {
470
+ getFederationMetrics(meterProvider).recordWebFingerHandle(attrs);
471
+ }
472
+ /**
427
473
  * Classifies a thrown value from a key or document fetch into the bounded
428
474
  * {@link LookupResult} taxonomy and, when an HTTP response was received,
429
475
  * surfaces its status code.
@@ -586,4 +632,4 @@ function getDurationMs(start) {
586
632
  return Math.max(0, performance.now() - start);
587
633
  }
588
634
  //#endregion
589
- export { instrumentDocumentLoader as a, recordDocumentCache as c, recordInboxActivity as d, recordKeyLookup as f, getRemoteHost as i, recordDocumentFetch as l, recordOutboxEnqueue as m, getDurationMs as n, isAbortError as o, recordOutboxActivity as p, getFederationMetrics as r, measureSignatureKeyFetch as s, classifyFetchError as t, recordFanoutRecipients as u };
635
+ export { instrumentDocumentLoader as a, recordDocumentCache as c, recordInboxActivity as d, recordKeyLookup as f, recordWebFingerHandle as h, getRemoteHost as i, recordDocumentFetch as l, recordOutboxEnqueue as m, getDurationMs as n, isAbortError as o, recordOutboxActivity as p, getFederationMetrics as r, measureSignatureKeyFetch as s, classifyFetchError as t, recordFanoutRecipients as u };
@@ -2,10 +2,10 @@ const { Temporal } = require("@js-temporal/polyfill");
2
2
  const { URLPattern } = require("urlpattern-polyfill");
3
3
  const require_chunk = require("./chunk-DDcVe30Y.cjs");
4
4
  const require_transformers = require("./transformers-NeAONrAq.cjs");
5
- const require_http = require("./http-CWoeyogl.cjs");
6
- const require_proof = require("./proof-CcsIJLTn.cjs");
5
+ const require_http = require("./http-7kAB7PVx.cjs");
6
+ const require_proof = require("./proof-jVqClF49.cjs");
7
7
  const require_types = require("./types-KC4QAoxe.cjs");
8
- const require_kv_cache = require("./kv-cache-DuEwFYcN.cjs");
8
+ const require_kv_cache = require("./kv-cache-DCPp-MT0.cjs");
9
9
  let _logtape_logtape = require("@logtape/logtape");
10
10
  let _fedify_uri_template = require("@fedify/uri-template");
11
11
  let _fedify_vocab = require("@fedify/vocab");
@@ -2601,22 +2601,72 @@ const logger = (0, _logtape_logtape.getLogger)([
2601
2601
  * @returns The response to the request.
2602
2602
  */
2603
2603
  async function handleWebFinger(request, options) {
2604
- if (options.tracer == null) return await handleWebFingerInternal(request, options);
2605
- return await options.tracer.startActiveSpan("webfinger.handle", { kind: _opentelemetry_api.SpanKind.SERVER }, async (span) => {
2606
- try {
2607
- const response = await handleWebFingerInternal(request, options);
2608
- span.setStatus({ code: response.ok ? _opentelemetry_api.SpanStatusCode.UNSET : _opentelemetry_api.SpanStatusCode.ERROR });
2609
- return response;
2610
- } catch (error) {
2611
- span.setStatus({
2612
- code: _opentelemetry_api.SpanStatusCode.ERROR,
2613
- message: String(error)
2614
- });
2615
- throw error;
2616
- } finally {
2617
- span.end();
2604
+ const meterProvider = options.meterProvider;
2605
+ const start = meterProvider == null ? 0 : performance.now();
2606
+ const scheme = computeResourceScheme(options.context.url.searchParams.get("resource"));
2607
+ let notFoundResponse;
2608
+ const wrappedOptions = {
2609
+ ...options,
2610
+ async onNotFound(req) {
2611
+ const r = await options.onNotFound(req);
2612
+ notFoundResponse = r;
2613
+ return r;
2618
2614
  }
2619
- });
2615
+ };
2616
+ let response;
2617
+ try {
2618
+ if (options.tracer == null) response = await handleWebFingerInternal(request, wrappedOptions);
2619
+ else response = await options.tracer.startActiveSpan("webfinger.handle", { kind: _opentelemetry_api.SpanKind.SERVER }, async (span) => {
2620
+ try {
2621
+ const inner = await handleWebFingerInternal(request, wrappedOptions);
2622
+ span.setStatus({ code: inner.ok ? _opentelemetry_api.SpanStatusCode.UNSET : _opentelemetry_api.SpanStatusCode.ERROR });
2623
+ return inner;
2624
+ } catch (error) {
2625
+ span.setStatus({
2626
+ code: _opentelemetry_api.SpanStatusCode.ERROR,
2627
+ message: String(error)
2628
+ });
2629
+ throw error;
2630
+ } finally {
2631
+ span.end();
2632
+ }
2633
+ });
2634
+ return response;
2635
+ } finally {
2636
+ if (meterProvider != null) require_http.recordWebFingerHandle(meterProvider, {
2637
+ durationMs: Math.max(0, performance.now() - start),
2638
+ result: classifyWebFingerHandleResult(response, notFoundResponse),
2639
+ scheme,
2640
+ statusCode: response?.status
2641
+ });
2642
+ }
2643
+ }
2644
+ const WEBFINGER_HANDLE_SCHEME_WHITELIST = new Set([
2645
+ "acct",
2646
+ "http",
2647
+ "https",
2648
+ "mailto"
2649
+ ]);
2650
+ function isAllowedResourceScheme(scheme) {
2651
+ return WEBFINGER_HANDLE_SCHEME_WHITELIST.has(scheme);
2652
+ }
2653
+ function computeResourceScheme(resource) {
2654
+ if (resource == null) return void 0;
2655
+ const colon = resource.indexOf(":");
2656
+ if (colon <= 0) return void 0;
2657
+ const candidate = resource.substring(0, colon).toLowerCase();
2658
+ return isAllowedResourceScheme(candidate) ? candidate : "other";
2659
+ }
2660
+ function classifyWebFingerHandleResult(response, notFoundResponse) {
2661
+ if (response == null) return "error";
2662
+ if (notFoundResponse != null && response === notFoundResponse) return "not_found";
2663
+ switch (response.status) {
2664
+ case 200: return "resolved";
2665
+ case 400: return "invalid";
2666
+ case 404: return "not_found";
2667
+ case 410: return "tombstoned";
2668
+ default: return "error";
2669
+ }
2620
2670
  }
2621
2671
  async function handleWebFingerInternal(request, { context, host, actorDispatcher, actorHandleMapper, actorAliasMapper, onNotFound, span, webFingerLinksDispatcher }) {
2622
2672
  if (actorDispatcher == null) {
@@ -3650,7 +3700,8 @@ var FederationImpl = class extends FederationBuilderImpl {
3650
3700
  actorAliasMapper: this.actorCallbacks?.aliasMapper,
3651
3701
  webFingerLinksDispatcher: this.webFingerLinksDispatcher,
3652
3702
  onNotFound,
3653
- tracer
3703
+ tracer,
3704
+ meterProvider: this._meterProvider
3654
3705
  });
3655
3706
  case "nodeInfoJrd": return await handleNodeInfoJrd(request, context);
3656
3707
  case "nodeInfo": return await handleNodeInfo(request, {
@@ -4209,6 +4260,7 @@ var ContextImpl = class ContextImpl {
4209
4260
  ...options,
4210
4261
  userAgent: options.userAgent ?? this.federation.userAgent,
4211
4262
  tracerProvider: options.tracerProvider ?? this.tracerProvider,
4263
+ meterProvider: options.meterProvider ?? this.federation._meterProvider,
4212
4264
  allowPrivateAddress: this.federation.allowPrivateAddress
4213
4265
  });
4214
4266
  }
@@ -2,10 +2,10 @@ import { Temporal } from "@js-temporal/polyfill";
2
2
  import { URLPattern } from "urlpattern-polyfill";
3
3
  import { t as __exportAll } from "./chunk-CRNNMoPX.js";
4
4
  import { r as getDefaultActivityTransformers } from "./transformers-BGMIq1cs.js";
5
- import { C as formatAcceptSignature, D as name, O as version, S as recordOutboxEnqueue, a as verifyRequestDetailed, b as recordInboxActivity, d as validateCryptoKey, f as getDurationMs, g as isAbortError, h as instrumentDocumentLoader, i as verifyRequest, m as getRemoteHost, n as parseRfc9421SignatureInput, o as exportJwk, p as getFederationMetrics, t as doubleKnock, u as importJwk, x as recordOutboxActivity, y as recordFanoutRecipients } from "./http-CToqG5ap.js";
6
- import { c as getKeyOwner, d as detachSignature, f as hasSignatureLike, i as verifyObject, m as verifyJsonLd, n as hasProofLike, o as normalizeOutgoingActivityJsonLd, p as signJsonLd, r as signObject, s as doesActorOwnKey } from "./proof-NRmtrTDu.js";
5
+ import { C as recordWebFingerHandle, O as name, S as recordOutboxEnqueue, a as verifyRequestDetailed, b as recordInboxActivity, d as validateCryptoKey, f as getDurationMs, g as isAbortError, h as instrumentDocumentLoader, i as verifyRequest, k as version, m as getRemoteHost, n as parseRfc9421SignatureInput, o as exportJwk, p as getFederationMetrics, t as doubleKnock, u as importJwk, w as formatAcceptSignature, x as recordOutboxActivity, y as recordFanoutRecipients } from "./http-B2hxA7dO.js";
6
+ import { c as getKeyOwner, d as detachSignature, f as hasSignatureLike, i as verifyObject, m as verifyJsonLd, n as hasProofLike, o as normalizeOutgoingActivityJsonLd, p as signJsonLd, r as signObject, s as doesActorOwnKey } from "./proof-BkRyFchv.js";
7
7
  import { n as getNodeInfo, t as nodeInfoToJson } from "./types-CAY3OdLq.js";
8
- import { n as getAuthenticatedDocumentLoader, t as kvCache } from "./kv-cache-CuCn2xvM.js";
8
+ import { n as getAuthenticatedDocumentLoader, t as kvCache } from "./kv-cache-b22dNkjt.js";
9
9
  import { getLogger, withContext } from "@logtape/logtape";
10
10
  import { Router, RouterError, assertPath } from "@fedify/uri-template";
11
11
  import { Activity, Collection, CollectionPage, CryptographicKey, Link, Multikey, Object as Object$1, OrderedCollection, OrderedCollectionPage, Tombstone, getTypeId, lookupObject, traverseCollection } from "@fedify/vocab";
@@ -2601,22 +2601,72 @@ const logger = getLogger([
2601
2601
  * @returns The response to the request.
2602
2602
  */
2603
2603
  async function handleWebFinger(request, options) {
2604
- if (options.tracer == null) return await handleWebFingerInternal(request, options);
2605
- return await options.tracer.startActiveSpan("webfinger.handle", { kind: SpanKind.SERVER }, async (span) => {
2606
- try {
2607
- const response = await handleWebFingerInternal(request, options);
2608
- span.setStatus({ code: response.ok ? SpanStatusCode.UNSET : SpanStatusCode.ERROR });
2609
- return response;
2610
- } catch (error) {
2611
- span.setStatus({
2612
- code: SpanStatusCode.ERROR,
2613
- message: String(error)
2614
- });
2615
- throw error;
2616
- } finally {
2617
- span.end();
2604
+ const meterProvider = options.meterProvider;
2605
+ const start = meterProvider == null ? 0 : performance.now();
2606
+ const scheme = computeResourceScheme(options.context.url.searchParams.get("resource"));
2607
+ let notFoundResponse;
2608
+ const wrappedOptions = {
2609
+ ...options,
2610
+ async onNotFound(req) {
2611
+ const r = await options.onNotFound(req);
2612
+ notFoundResponse = r;
2613
+ return r;
2618
2614
  }
2619
- });
2615
+ };
2616
+ let response;
2617
+ try {
2618
+ if (options.tracer == null) response = await handleWebFingerInternal(request, wrappedOptions);
2619
+ else response = await options.tracer.startActiveSpan("webfinger.handle", { kind: SpanKind.SERVER }, async (span) => {
2620
+ try {
2621
+ const inner = await handleWebFingerInternal(request, wrappedOptions);
2622
+ span.setStatus({ code: inner.ok ? SpanStatusCode.UNSET : SpanStatusCode.ERROR });
2623
+ return inner;
2624
+ } catch (error) {
2625
+ span.setStatus({
2626
+ code: SpanStatusCode.ERROR,
2627
+ message: String(error)
2628
+ });
2629
+ throw error;
2630
+ } finally {
2631
+ span.end();
2632
+ }
2633
+ });
2634
+ return response;
2635
+ } finally {
2636
+ if (meterProvider != null) recordWebFingerHandle(meterProvider, {
2637
+ durationMs: Math.max(0, performance.now() - start),
2638
+ result: classifyWebFingerHandleResult(response, notFoundResponse),
2639
+ scheme,
2640
+ statusCode: response?.status
2641
+ });
2642
+ }
2643
+ }
2644
+ const WEBFINGER_HANDLE_SCHEME_WHITELIST = new Set([
2645
+ "acct",
2646
+ "http",
2647
+ "https",
2648
+ "mailto"
2649
+ ]);
2650
+ function isAllowedResourceScheme(scheme) {
2651
+ return WEBFINGER_HANDLE_SCHEME_WHITELIST.has(scheme);
2652
+ }
2653
+ function computeResourceScheme(resource) {
2654
+ if (resource == null) return void 0;
2655
+ const colon = resource.indexOf(":");
2656
+ if (colon <= 0) return void 0;
2657
+ const candidate = resource.substring(0, colon).toLowerCase();
2658
+ return isAllowedResourceScheme(candidate) ? candidate : "other";
2659
+ }
2660
+ function classifyWebFingerHandleResult(response, notFoundResponse) {
2661
+ if (response == null) return "error";
2662
+ if (notFoundResponse != null && response === notFoundResponse) return "not_found";
2663
+ switch (response.status) {
2664
+ case 200: return "resolved";
2665
+ case 400: return "invalid";
2666
+ case 404: return "not_found";
2667
+ case 410: return "tombstoned";
2668
+ default: return "error";
2669
+ }
2620
2670
  }
2621
2671
  async function handleWebFingerInternal(request, { context, host, actorDispatcher, actorHandleMapper, actorAliasMapper, onNotFound, span, webFingerLinksDispatcher }) {
2622
2672
  if (actorDispatcher == null) {
@@ -3650,7 +3700,8 @@ var FederationImpl = class extends FederationBuilderImpl {
3650
3700
  actorAliasMapper: this.actorCallbacks?.aliasMapper,
3651
3701
  webFingerLinksDispatcher: this.webFingerLinksDispatcher,
3652
3702
  onNotFound,
3653
- tracer
3703
+ tracer,
3704
+ meterProvider: this._meterProvider
3654
3705
  });
3655
3706
  case "nodeInfoJrd": return await handleNodeInfoJrd(request, context);
3656
3707
  case "nodeInfo": return await handleNodeInfo(request, {
@@ -4209,6 +4260,7 @@ var ContextImpl = class ContextImpl {
4209
4260
  ...options,
4210
4261
  userAgent: options.userAgent ?? this.federation.userAgent,
4211
4262
  tracerProvider: options.tracerProvider ?? this.tracerProvider,
4263
+ meterProvider: options.meterProvider ?? this.federation._meterProvider,
4212
4264
  allowPrivateAddress: this.federation.allowPrivateAddress
4213
4265
  });
4214
4266
  }
@@ -1,25 +1,25 @@
1
1
  import { Temporal } from "@js-temporal/polyfill";
2
2
  import "urlpattern-polyfill";
3
3
  globalThis.addEventListener = () => {};
4
- import { n as version, t as name } from "./deno-B_9yJW3w.mjs";
5
- import { a as instrumentDocumentLoader, d as recordInboxActivity, i as getRemoteHost, m as recordOutboxEnqueue, n as getDurationMs, o as isAbortError, p as recordOutboxActivity, r as getFederationMetrics, u as recordFanoutRecipients } from "./metrics-iRBg8jTk.mjs";
4
+ import { n as version, t as name } from "./deno-h0TWFuEz.mjs";
5
+ import { a as instrumentDocumentLoader, d as recordInboxActivity, h as recordWebFingerHandle, i as getRemoteHost, m as recordOutboxEnqueue, n as getDurationMs, o as isAbortError, p as recordOutboxActivity, r as getFederationMetrics, u as recordFanoutRecipients } from "./metrics-E0hAHtLZ.mjs";
6
6
  import { t as formatAcceptSignature } from "./accept-CceiKpCy.mjs";
7
- import { a as importJwk, o as validateCryptoKey, t as exportJwk } from "./key-CkkMJBjF.mjs";
8
- import { l as verifyRequest, o as parseRfc9421SignatureInput, u as verifyRequestDetailed } from "./http-Cyx5SNuu.mjs";
9
- import { t as getAuthenticatedDocumentLoader } from "./docloader-BT89tyFr.mjs";
10
- import { n as kvCache } from "./kv-cache-VHFP42vY.mjs";
11
- import { a as signJsonLd, i as hasSignatureLike, o as verifyJsonLd, r as detachSignature } from "./ld-k8yqD2a-.mjs";
12
- import { n as getKeyOwner, t as doesActorOwnKey } from "./owner-nmXdvXpc.mjs";
7
+ import { a as importJwk, o as validateCryptoKey, t as exportJwk } from "./key-Dh2OK1XQ.mjs";
8
+ import { l as verifyRequest, o as parseRfc9421SignatureInput, u as verifyRequestDetailed } from "./http-QzW9IWfs.mjs";
9
+ import { t as getAuthenticatedDocumentLoader } from "./docloader-BdDN0Aqx.mjs";
10
+ import { n as kvCache } from "./kv-cache-EZRIPZXD.mjs";
11
+ import { a as signJsonLd, i as hasSignatureLike, o as verifyJsonLd, r as detachSignature } from "./ld-eZbar1rr.mjs";
12
+ import { n as getKeyOwner, t as doesActorOwnKey } from "./owner-ByO_Fw6U.mjs";
13
13
  import { r as normalizeOutgoingActivityJsonLd } from "./outgoing-jsonld-BgFLCJQ_.mjs";
14
- import { i as verifyObject, n as hasProofLike, r as signObject } from "./proof-DpwO1T4S.mjs";
14
+ import { i as verifyObject, n as hasProofLike, r as signObject } from "./proof-CSo0S8OK.mjs";
15
15
  import { t as getNodeInfo } from "./client-B_A6mfn3.mjs";
16
16
  import { t as nodeInfoToJson } from "./types-BFowWFTT.mjs";
17
- import { n as FederationBuilderImpl, t as ACTOR_ALIAS_PREFIX } from "./builder-BCkBXxky.mjs";
17
+ import { n as FederationBuilderImpl, t as ACTOR_ALIAS_PREFIX } from "./builder-ShiR1K6b.mjs";
18
18
  import { t as buildCollectionSynchronizationHeader } from "./collection-CA3V5zyK.mjs";
19
19
  import { t as KvKeyCache } from "./keycache-BYMd8q7F.mjs";
20
20
  import { t as acceptsJsonLd } from "./negotiation-CDW-_gUU.mjs";
21
21
  import { t as createExponentialBackoffPolicy } from "./retry-v_sGLH1d.mjs";
22
- import { n as extractInboxes, r as sendActivity, t as SendActivityError } from "./send-DvX2tYyZ.mjs";
22
+ import { n as extractInboxes, r as sendActivity, t as SendActivityError } from "./send-jzrTV1FU.mjs";
23
23
  import { getLogger, withContext } from "@logtape/logtape";
24
24
  import { RouterError } from "@fedify/uri-template";
25
25
  import { Activity, Collection, CollectionPage, CryptographicKey, Link, Multikey, Object as Object$1, OrderedCollection, OrderedCollectionPage, Tombstone, getTypeId, lookupObject, traverseCollection } from "@fedify/vocab";
@@ -1600,22 +1600,72 @@ const logger = getLogger([
1600
1600
  * @returns The response to the request.
1601
1601
  */
1602
1602
  async function handleWebFinger(request, options) {
1603
- if (options.tracer == null) return await handleWebFingerInternal(request, options);
1604
- return await options.tracer.startActiveSpan("webfinger.handle", { kind: SpanKind.SERVER }, async (span) => {
1605
- try {
1606
- const response = await handleWebFingerInternal(request, options);
1607
- span.setStatus({ code: response.ok ? SpanStatusCode.UNSET : SpanStatusCode.ERROR });
1608
- return response;
1609
- } catch (error) {
1610
- span.setStatus({
1611
- code: SpanStatusCode.ERROR,
1612
- message: String(error)
1613
- });
1614
- throw error;
1615
- } finally {
1616
- span.end();
1603
+ const meterProvider = options.meterProvider;
1604
+ const start = meterProvider == null ? 0 : performance.now();
1605
+ const scheme = computeResourceScheme(options.context.url.searchParams.get("resource"));
1606
+ let notFoundResponse;
1607
+ const wrappedOptions = {
1608
+ ...options,
1609
+ async onNotFound(req) {
1610
+ const r = await options.onNotFound(req);
1611
+ notFoundResponse = r;
1612
+ return r;
1617
1613
  }
1618
- });
1614
+ };
1615
+ let response;
1616
+ try {
1617
+ if (options.tracer == null) response = await handleWebFingerInternal(request, wrappedOptions);
1618
+ else response = await options.tracer.startActiveSpan("webfinger.handle", { kind: SpanKind.SERVER }, async (span) => {
1619
+ try {
1620
+ const inner = await handleWebFingerInternal(request, wrappedOptions);
1621
+ span.setStatus({ code: inner.ok ? SpanStatusCode.UNSET : SpanStatusCode.ERROR });
1622
+ return inner;
1623
+ } catch (error) {
1624
+ span.setStatus({
1625
+ code: SpanStatusCode.ERROR,
1626
+ message: String(error)
1627
+ });
1628
+ throw error;
1629
+ } finally {
1630
+ span.end();
1631
+ }
1632
+ });
1633
+ return response;
1634
+ } finally {
1635
+ if (meterProvider != null) recordWebFingerHandle(meterProvider, {
1636
+ durationMs: Math.max(0, performance.now() - start),
1637
+ result: classifyWebFingerHandleResult(response, notFoundResponse),
1638
+ scheme,
1639
+ statusCode: response?.status
1640
+ });
1641
+ }
1642
+ }
1643
+ const WEBFINGER_HANDLE_SCHEME_WHITELIST = new Set([
1644
+ "acct",
1645
+ "http",
1646
+ "https",
1647
+ "mailto"
1648
+ ]);
1649
+ function isAllowedResourceScheme(scheme) {
1650
+ return WEBFINGER_HANDLE_SCHEME_WHITELIST.has(scheme);
1651
+ }
1652
+ function computeResourceScheme(resource) {
1653
+ if (resource == null) return void 0;
1654
+ const colon = resource.indexOf(":");
1655
+ if (colon <= 0) return void 0;
1656
+ const candidate = resource.substring(0, colon).toLowerCase();
1657
+ return isAllowedResourceScheme(candidate) ? candidate : "other";
1658
+ }
1659
+ function classifyWebFingerHandleResult(response, notFoundResponse) {
1660
+ if (response == null) return "error";
1661
+ if (notFoundResponse != null && response === notFoundResponse) return "not_found";
1662
+ switch (response.status) {
1663
+ case 200: return "resolved";
1664
+ case 400: return "invalid";
1665
+ case 404: return "not_found";
1666
+ case 410: return "tombstoned";
1667
+ default: return "error";
1668
+ }
1619
1669
  }
1620
1670
  async function handleWebFingerInternal(request, { context, host, actorDispatcher, actorHandleMapper, actorAliasMapper, onNotFound, span, webFingerLinksDispatcher }) {
1621
1671
  if (actorDispatcher == null) {
@@ -2641,7 +2691,8 @@ var FederationImpl = class extends FederationBuilderImpl {
2641
2691
  actorAliasMapper: this.actorCallbacks?.aliasMapper,
2642
2692
  webFingerLinksDispatcher: this.webFingerLinksDispatcher,
2643
2693
  onNotFound,
2644
- tracer
2694
+ tracer,
2695
+ meterProvider: this._meterProvider
2645
2696
  });
2646
2697
  case "nodeInfoJrd": return await handleNodeInfoJrd(request, context);
2647
2698
  case "nodeInfo": return await handleNodeInfo(request, {
@@ -3200,6 +3251,7 @@ var ContextImpl = class ContextImpl {
3200
3251
  ...options,
3201
3252
  userAgent: options.userAgent ?? this.federation.userAgent,
3202
3253
  tracerProvider: options.tracerProvider ?? this.tracerProvider,
3254
+ meterProvider: options.meterProvider ?? this.federation._meterProvider,
3203
3255
  allowPrivateAddress: this.federation.allowPrivateAddress
3204
3256
  });
3205
3257
  }
@@ -1,5 +1,5 @@
1
1
  import "@js-temporal/polyfill";
2
2
  import "urlpattern-polyfill";
3
3
  globalThis.addEventListener = () => {};
4
- import { n as FederationImpl } from "./middleware-DQEgdr83.mjs";
4
+ import { n as FederationImpl } from "./middleware-CyJDCmNg.mjs";
5
5
  export { FederationImpl };
@@ -1,6 +1,6 @@
1
1
  /// <reference lib="esnext.temporal" />
2
2
  import { G as ActorHandleMapper, U as ActorAliasMapper, W as ActorDispatcher, ft as WebFingerLinksDispatcher, l as RequestContext } from "./context-DI2gRbyN.cjs";
3
- import { Span, Tracer } from "@opentelemetry/api";
3
+ import { MeterProvider, Span, Tracer } from "@opentelemetry/api";
4
4
  import { RouterError, RouterOptions, RouterRouteResult } from "@fedify/uri-template";
5
5
 
6
6
  //#region src/federation/router.d.ts
@@ -160,6 +160,15 @@ interface WebFingerHandlerParameters<TContextData> {
160
160
  * @since 1.3.0
161
161
  */
162
162
  span?: Span;
163
+ /**
164
+ * The OpenTelemetry meter provider used to record the `webfinger.handle`
165
+ * counter and `webfinger.handle.duration` histogram. When omitted, no
166
+ * WebFinger-specific measurements are emitted (the request still
167
+ * contributes to `fedify.http.server.request.*` because that metric is
168
+ * recorded one layer up in `Federation.fetch`).
169
+ * @since 2.3.0
170
+ */
171
+ meterProvider?: MeterProvider;
163
172
  }
164
173
  /**
165
174
  * Handles a WebFinger request. You would not typically call this function
@@ -1,7 +1,7 @@
1
1
  /// <reference lib="esnext.temporal" />
2
2
  import { G as ActorHandleMapper, U as ActorAliasMapper, W as ActorDispatcher, ft as WebFingerLinksDispatcher, l as RequestContext } from "./context-DCtsSHDv.js";
3
3
  import { RouterError, RouterOptions, RouterRouteResult } from "@fedify/uri-template";
4
- import { Span, Tracer } from "@opentelemetry/api";
4
+ import { MeterProvider, Span, Tracer } from "@opentelemetry/api";
5
5
 
6
6
  //#region src/federation/router.d.ts
7
7
  /**
@@ -160,6 +160,15 @@ interface WebFingerHandlerParameters<TContextData> {
160
160
  * @since 1.3.0
161
161
  */
162
162
  span?: Span;
163
+ /**
164
+ * The OpenTelemetry meter provider used to record the `webfinger.handle`
165
+ * counter and `webfinger.handle.duration` histogram. When omitted, no
166
+ * WebFinger-specific measurements are emitted (the request still
167
+ * contributes to `fedify.http.server.request.*` because that metric is
168
+ * recorded one layer up in `Federation.fetch`).
169
+ * @since 2.3.0
170
+ */
171
+ meterProvider?: MeterProvider;
163
172
  }
164
173
  /**
165
174
  * Handles a WebFinger request. You would not typically call this function
package/dist/mod.cjs CHANGED
@@ -4,11 +4,11 @@ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
4
4
  require("./chunk-DDcVe30Y.cjs");
5
5
  const require_transformers = require("./transformers-NeAONrAq.cjs");
6
6
  require("./compat/mod.cjs");
7
- const require_http = require("./http-CWoeyogl.cjs");
8
- const require_middleware = require("./middleware-BWLUrbS9.cjs");
9
- const require_proof = require("./proof-CcsIJLTn.cjs");
7
+ const require_http = require("./http-7kAB7PVx.cjs");
8
+ const require_middleware = require("./middleware-BUl1BH4x.cjs");
9
+ const require_proof = require("./proof-jVqClF49.cjs");
10
10
  const require_types = require("./types-KC4QAoxe.cjs");
11
- const require_kv_cache = require("./kv-cache-DuEwFYcN.cjs");
11
+ const require_kv_cache = require("./kv-cache-DCPp-MT0.cjs");
12
12
  const require_federation_mod = require("./federation/mod.cjs");
13
13
  require("./nodeinfo/mod.cjs");
14
14
  require("./runtime/mod.cjs");
package/dist/mod.d.cts CHANGED
@@ -6,7 +6,7 @@ import { $ as CustomCollectionDispatcher, A as FederationKvPrefixes, B as Respon
6
6
  import { a as MemoryKvStore, i as KvStoreSetOptions, n as KvStore, r as KvStoreListEntry, t as KvKey } from "./kv-gJ8LYbxX.cjs";
7
7
  import { a as MessageQueueEnqueueOptions, i as MessageQueueDepth, n as InProcessMessageQueueOptions, o as MessageQueueListenOptions, r as MessageQueue, s as ParallelMessageQueue, t as InProcessMessageQueue } from "./mq-D8uSFzxe.cjs";
8
8
  import { actorDehydrator, autoIdAssigner, getDefaultActivityTransformers } from "./compat/mod.cjs";
9
- import { a as RouterOptions, i as RouterError, n as handleWebFinger, o as RouterRouteResult, r as Router, t as WebFingerHandlerParameters } from "./mod-C504qevA.cjs";
9
+ import { a as RouterOptions, i as RouterError, n as handleWebFinger, o as RouterRouteResult, r as Router, t as WebFingerHandlerParameters } from "./mod-CI9fduEi.cjs";
10
10
  import { _ as hasSignatureLike, a as createProof, b as verifySignature, c as verifyObject, d as SignJsonLdOptions, f as VerifyJsonLdOptions, g as detachSignature, h as createSignature, i as VerifyProofOptions, l as verifyProof, m as attachSignature, n as SignObjectOptions, o as hasProofLike, p as VerifySignatureOptions, r as VerifyObjectOptions, s as signObject, t as CreateProofOptions, u as CreateSignatureOptions, v as signJsonLd, y as verifyJsonLd } from "./mod-B0hW12_O.cjs";
11
11
  import { n as getAuthenticatedDocumentLoader, t as kvCache } from "./mod-yvIXFAEi.cjs";
12
12
  export * from "@fedify/vocab-runtime";
package/dist/mod.d.ts CHANGED
@@ -6,7 +6,7 @@ import { $ as CustomCollectionDispatcher, A as FederationKvPrefixes, B as Respon
6
6
  import { a as MemoryKvStore, i as KvStoreSetOptions, n as KvStore, r as KvStoreListEntry, t as KvKey } from "./kv-D6hNiMTK.js";
7
7
  import { a as MessageQueueEnqueueOptions, i as MessageQueueDepth, n as InProcessMessageQueueOptions, o as MessageQueueListenOptions, r as MessageQueue, s as ParallelMessageQueue, t as InProcessMessageQueue } from "./mq-D-nlpY04.js";
8
8
  import { actorDehydrator, autoIdAssigner, getDefaultActivityTransformers } from "./compat/mod.js";
9
- import { a as RouterOptions, i as RouterError, n as handleWebFinger, o as RouterRouteResult, r as Router, t as WebFingerHandlerParameters } from "./mod-wYfuXeDE.js";
9
+ import { a as RouterOptions, i as RouterError, n as handleWebFinger, o as RouterRouteResult, r as Router, t as WebFingerHandlerParameters } from "./mod-CkRiJHGA.js";
10
10
  import { _ as hasSignatureLike, a as createProof, b as verifySignature, c as verifyObject, d as SignJsonLdOptions, f as VerifyJsonLdOptions, g as detachSignature, h as createSignature, i as VerifyProofOptions, l as verifyProof, m as attachSignature, n as SignObjectOptions, o as hasProofLike, p as VerifySignatureOptions, r as VerifyObjectOptions, s as signObject, t as CreateProofOptions, u as CreateSignatureOptions, v as signJsonLd, y as verifyJsonLd } from "./mod-COIAjwRS.js";
11
11
  import { n as getAuthenticatedDocumentLoader, t as kvCache } from "./mod-DFvNJcNb.js";
12
12
  export * from "@fedify/vocab-runtime";
package/dist/mod.js CHANGED
@@ -3,11 +3,11 @@ import { URLPattern } from "urlpattern-polyfill";
3
3
  import "./chunk-CRNNMoPX.js";
4
4
  import { n as autoIdAssigner, r as getDefaultActivityTransformers, t as actorDehydrator } from "./transformers-BGMIq1cs.js";
5
5
  import "./compat/mod.js";
6
- import { C as formatAcceptSignature, E as validateAcceptSignature, T as parseAcceptSignature, a as verifyRequestDetailed, c as fetchKeyDetailed, i as verifyRequest, l as generateCryptoKeyPair, o as exportJwk, r as signRequest, s as fetchKey, u as importJwk, w as fulfillAcceptSignature } from "./http-CToqG5ap.js";
7
- import { a as createExponentialBackoffPolicy, c as buildCollectionSynchronizationHeader, i as SendActivityError, l as digest, o as respondWithObject, r as handleWebFinger, s as respondWithObjectIfAcceptable, t as createFederation, u as createFederationBuilder } from "./middleware-D7FrhN9q.js";
8
- import { a as verifyProof, c as getKeyOwner, d as detachSignature, f as hasSignatureLike, h as verifySignature, i as verifyObject, l as attachSignature, m as verifyJsonLd, n as hasProofLike, p as signJsonLd, r as signObject, s as doesActorOwnKey, t as createProof, u as createSignature } from "./proof-NRmtrTDu.js";
6
+ import { D as validateAcceptSignature, E as parseAcceptSignature, T as fulfillAcceptSignature, a as verifyRequestDetailed, c as fetchKeyDetailed, i as verifyRequest, l as generateCryptoKeyPair, o as exportJwk, r as signRequest, s as fetchKey, u as importJwk, w as formatAcceptSignature } from "./http-B2hxA7dO.js";
7
+ import { a as createExponentialBackoffPolicy, c as buildCollectionSynchronizationHeader, i as SendActivityError, l as digest, o as respondWithObject, r as handleWebFinger, s as respondWithObjectIfAcceptable, t as createFederation, u as createFederationBuilder } from "./middleware-BrGIM_Ra.js";
8
+ import { a as verifyProof, c as getKeyOwner, d as detachSignature, f as hasSignatureLike, h as verifySignature, i as verifyObject, l as attachSignature, m as verifyJsonLd, n as hasProofLike, p as signJsonLd, r as signObject, s as doesActorOwnKey, t as createProof, u as createSignature } from "./proof-BkRyFchv.js";
9
9
  import { n as getNodeInfo, r as parseNodeInfo, t as nodeInfoToJson } from "./types-CAY3OdLq.js";
10
- import { n as getAuthenticatedDocumentLoader, t as kvCache } from "./kv-cache-CuCn2xvM.js";
10
+ import { n as getAuthenticatedDocumentLoader, t as kvCache } from "./kv-cache-b22dNkjt.js";
11
11
  import { InProcessMessageQueue, MemoryKvStore, ParallelMessageQueue, Router, RouterError } from "./federation/mod.js";
12
12
  import "./nodeinfo/mod.js";
13
13
  import "./runtime/mod.js";
@@ -5,7 +5,7 @@ import { r as createRequestContext } from "../context-DVoTs_wM.mjs";
5
5
  import { t as assertEquals } from "../assert_equals-C-ZRDbaf.mjs";
6
6
  import "../std__assert-BBjXFNOb.mjs";
7
7
  import { t as MemoryKvStore } from "../kv-x2IvBUyq.mjs";
8
- import { _ as handleNodeInfoJrd, g as handleNodeInfo, o as createFederation } from "../middleware-DQEgdr83.mjs";
8
+ import { _ as handleNodeInfoJrd, g as handleNodeInfo, o as createFederation } from "../middleware-CyJDCmNg.mjs";
9
9
  import { test } from "@fedify/fixture";
10
10
  //#region src/nodeinfo/handler.test.ts
11
11
  test("handleNodeInfo()", async () => {
@@ -1,8 +1,8 @@
1
1
  import "@js-temporal/polyfill";
2
2
  import "urlpattern-polyfill";
3
3
  globalThis.addEventListener = () => {};
4
- import { n as version, t as name } from "./deno-B_9yJW3w.mjs";
5
- import "./key-CkkMJBjF.mjs";
4
+ import { n as version, t as name } from "./deno-h0TWFuEz.mjs";
5
+ import "./key-Dh2OK1XQ.mjs";
6
6
  import { CryptographicKey, Object as Object$1, isActor } from "@fedify/vocab";
7
7
  import { SpanKind, SpanStatusCode, trace } from "@opentelemetry/api";
8
8
  import { getDocumentLoader } from "@fedify/vocab-runtime";