@fedify/fedify 2.3.0-dev.1190 → 2.3.0-dev.1213

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 (83) hide show
  1. package/dist/{builder-BzgNpXoY.mjs → builder-Bj-7Sl7u.mjs} +9 -2
  2. package/dist/compat/mod.d.cts +1 -1
  3. package/dist/compat/mod.d.ts +1 -1
  4. package/dist/compat/outgoing-jsonld.test.mjs +1 -1
  5. package/dist/compat/public-audience.test.mjs +1 -1
  6. package/dist/compat/transformers.test.mjs +2 -2
  7. package/dist/{context-DMHK7jqX.d.cts → context-BBVLF7lx.d.cts} +41 -2
  8. package/dist/{context-K9cg8oGx.d.ts → context-BU6jSQdo.d.ts} +42 -2
  9. package/dist/{deno-CoAwVm1I.mjs → deno-BUzynMVz.mjs} +1 -1
  10. package/dist/{docloader-hPqZT20O.mjs → docloader-jQPthO4U.mjs} +2 -2
  11. package/dist/{esm-BQRw925N.mjs → esm-vrlUxr60.mjs} +23 -1
  12. package/dist/federation/builder.test.mjs +21 -2
  13. package/dist/federation/circuit-breaker.test.mjs +1 -1
  14. package/dist/federation/handler.test.mjs +6 -6
  15. package/dist/federation/idempotency.test.mjs +4 -4
  16. package/dist/federation/keycache.test.mjs +1 -1
  17. package/dist/federation/kv.test.mjs +1 -1
  18. package/dist/federation/metrics.test.mjs +147 -1
  19. package/dist/federation/middleware.test.mjs +446 -18
  20. package/dist/federation/mod.cjs +1 -1
  21. package/dist/federation/mod.d.cts +3 -3
  22. package/dist/federation/mod.d.ts +3 -3
  23. package/dist/federation/mod.js +1 -1
  24. package/dist/federation/send.test.mjs +8 -4426
  25. package/dist/federation/temporal.test.mjs +1 -1
  26. package/dist/federation/webfinger.test.mjs +2 -2
  27. package/dist/{http-BAarxBe5.cjs → http-B1zlPuh3.cjs} +79 -2
  28. package/dist/{http-Dq_qElWc.js → http-B_WbYMnB.js} +74 -3
  29. package/dist/{http-CSwCAQ-H.mjs → http-CDaMGwCP.mjs} +4 -4
  30. package/dist/{key-DYK_T_PD.mjs → key-DdP4HxTK.mjs} +2 -2
  31. package/dist/{kv-cache-Ds1kjvnu.cjs → kv-cache-CPIfTWt5.cjs} +1 -1
  32. package/dist/{kv-cache-BhPocHdd.js → kv-cache-CXo8QM4m.js} +1 -1
  33. package/dist/{kv-cache-CFzIDCMJ.mjs → kv-cache-DSjv5Aeh.mjs} +1 -1
  34. package/dist/{ld-BdcT_irA.mjs → ld-CuOEh5aB.mjs} +3 -3
  35. package/dist/{metrics-Ci97wkob.mjs → metrics-B5vvJYMV.mjs} +74 -3
  36. package/dist/{middleware-BUGT2LmO.mjs → middleware-Cq9S8A5O.mjs} +328 -24
  37. package/dist/{middleware-hWs3qtrr.mjs → middleware-DPE-IRlD.mjs} +1 -1
  38. package/dist/{middleware-C-C_I_wJ.js → middleware-Dft_sYeS.js} +352 -41
  39. package/dist/{middleware-ddMAHsyF.cjs → middleware-DlqW4IRW.cjs} +351 -40
  40. package/dist/{mod-YLnSsEHY.d.cts → mod-C0F6kvgS.d.cts} +1 -1
  41. package/dist/{mod-CfOFqS0w.d.ts → mod-vPYVoa5n.d.ts} +1 -1
  42. package/dist/mod.cjs +4 -4
  43. package/dist/mod.d.cts +4 -4
  44. package/dist/mod.d.ts +4 -4
  45. package/dist/mod.js +4 -4
  46. package/dist/nodeinfo/client.test.mjs +2 -2
  47. package/dist/nodeinfo/handler.test.mjs +2 -2
  48. package/dist/nodeinfo/types.test.mjs +1 -1
  49. package/dist/otel/exporter.test.mjs +1 -1
  50. package/dist/{outgoing-jsonld-BgFLCJQ_.mjs → outgoing-jsonld-L_DbOaFe.mjs} +1 -1
  51. package/dist/{owner-B8ePZh4q.mjs → owner--n8rmG51.mjs} +2 -2
  52. package/dist/{proof-CXdtqYKw.cjs → proof-BKpJ_p_d.cjs} +1 -1
  53. package/dist/{proof-Dq_RyTjd.js → proof-BsvB1vGI.js} +1 -1
  54. package/dist/{proof-CzqluPMh.mjs → proof-dhtwaP4z.mjs} +5 -5
  55. package/dist/{send-NzJqiStx.mjs → send-CTQ30Wbe.mjs} +3 -3
  56. package/dist/sig/accept.test.mjs +1 -1
  57. package/dist/sig/http.test.mjs +4 -4
  58. package/dist/sig/key.test.mjs +2 -2
  59. package/dist/sig/ld.test.mjs +3 -3
  60. package/dist/sig/mod.cjs +2 -2
  61. package/dist/sig/mod.js +2 -2
  62. package/dist/sig/owner.test.mjs +2 -2
  63. package/dist/sig/proof.test.mjs +3 -3
  64. package/dist/{temporal-CnhE0LLn.mjs → temporal-gfUaZjGU.mjs} +1 -1
  65. package/dist/testing/mod.d.mts +1 -0
  66. package/dist/utils/docloader.test.mjs +4 -4
  67. package/dist/utils/kv-cache.test.mjs +1 -1
  68. package/dist/utils/mod.cjs +1 -1
  69. package/dist/utils/mod.js +1 -1
  70. package/package.json +8 -8
  71. package/dist/chunk-DNRtMIoB.mjs +0 -29
  72. package/dist/execAsync-Dmet7-28.mjs +0 -13
  73. package/dist/getMachineId-bsd-Bn0le7-J.mjs +0 -29
  74. package/dist/getMachineId-darwin-CVjKuDgj.mjs +0 -26
  75. package/dist/getMachineId-linux-DbG4BXa-.mjs +0 -22
  76. package/dist/getMachineId-unsupported-lC8T9hPE.mjs +0 -17
  77. package/dist/getMachineId-win-c5zxTSS1.mjs +0 -28
  78. /package/dist/{accept-CceiKpCy.mjs → accept-CPkZzmGN.mjs} +0 -0
  79. /package/dist/{client-B_A6mfn3.mjs → client-ByXmQhYD.mjs} +0 -0
  80. /package/dist/{keys-C3kae-6B.mjs → keys-DGu1NFwu.mjs} +0 -0
  81. /package/dist/{kv-x2IvBUyq.mjs → kv-rV3vodCc.mjs} +0 -0
  82. /package/dist/{public-audience-N3pyOx2p.mjs → public-audience-Cvbr2Gzt.mjs} +0 -0
  83. /package/dist/{types-BFowWFTT.mjs → types-J53Kw7so.mjs} +0 -0
@@ -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 { D as recordOutboxEnqueue, E as recordOutboxActivity, N as name, O as recordWebFingerHandle, P as version, S as recordCollectionTotalItems, T as recordInboxActivity, a as verifyRequestDetailed, b as recordCollectionPageItems, d as validateCryptoKey, f as getDurationMs, g as isAbortError, h as instrumentDocumentLoader, i as verifyRequest, k as formatAcceptSignature, m as getRemoteHost, n as parseRfc9421SignatureInput, o as exportJwk, p as getFederationMetrics, t as doubleKnock, u as importJwk, v as recordCircuitBreakerStateChange, w as recordFanoutRecipients, x as recordCollectionRequest, y as recordCollectionDispatchDuration } from "./http-Dq_qElWc.js";
6
- import { _ as hasSignatureLike, b as signJsonLd, c as getKeyOwner, f as compactJsonLd, g as hasSignature, h as getNormalizationContextLoader, i as verifyObject, l as InvalidContextReferenceError, m as detachSignature, n as hasProofLike, o as normalizeOutgoingActivityJsonLd, r as signObject, s as doesActorOwnKey, u as assertSafeJsonLd, v as isClearlyMalformedContextReference, w as wrapContextLoaderForJsonLd, x as verifyCompactJsonLd, y as isInvalidUrlTypeError } from "./proof-Dq_RyTjd.js";
5
+ import { A as formatAcceptSignature, D as recordOutboxEnqueue, E as recordOutboxActivity, F as version, O as recordWebFingerHandle, P as name, S as recordCollectionTotalItems, T as recordInboxActivity, a as verifyRequestDetailed, b as recordCollectionPageItems, d as validateCryptoKey, f as getDurationMs, g as isAbortError, h as instrumentDocumentLoader, i as verifyRequest, k as registerQueueDepthGauge, m as getRemoteHost, n as parseRfc9421SignatureInput, o as exportJwk, p as getFederationMetrics, t as doubleKnock, u as importJwk, v as recordCircuitBreakerStateChange, w as recordFanoutRecipients, x as recordCollectionRequest, y as recordCollectionDispatchDuration } from "./http-B_WbYMnB.js";
6
+ import { _ as hasSignatureLike, b as signJsonLd, c as getKeyOwner, f as compactJsonLd, g as hasSignature, h as getNormalizationContextLoader, i as verifyObject, l as InvalidContextReferenceError, m as detachSignature, n as hasProofLike, o as normalizeOutgoingActivityJsonLd, r as signObject, s as doesActorOwnKey, u as assertSafeJsonLd, v as isClearlyMalformedContextReference, w as wrapContextLoaderForJsonLd, x as verifyCompactJsonLd, y as isInvalidUrlTypeError } from "./proof-BsvB1vGI.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-BhPocHdd.js";
8
+ import { n as getAuthenticatedDocumentLoader, t as kvCache } from "./kv-cache-CXo8QM4m.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";
@@ -16,6 +16,7 @@ import { FetchError, getDocumentLoader } from "@fedify/vocab-runtime";
16
16
  import { ATTR_HTTP_REQUEST_HEADER, ATTR_HTTP_REQUEST_METHOD, ATTR_HTTP_RESPONSE_HEADER, ATTR_HTTP_RESPONSE_STATUS_CODE, ATTR_URL_FULL } from "@opentelemetry/semantic-conventions";
17
17
  import jsonld from "@fedify/vocab-runtime/jsonld";
18
18
  import { lookupWebFinger } from "@fedify/webfinger";
19
+ import { DataPointType, MeterProvider as MeterProvider$1, MetricReader } from "@opentelemetry/sdk-metrics";
19
20
  import { domainToASCII } from "node:url";
20
21
  //#region src/federation/activity-listener.ts
21
22
  var ActivityListenerSet = class {
@@ -117,6 +118,13 @@ var FederationBuilderImpl = class {
117
118
  this.collectionCallbacks = {};
118
119
  this.collectionTypeIds = {};
119
120
  }
121
+ /**
122
+ * Builds the federation object.
123
+ * @param options Parameters for initializing the federation object.
124
+ * @returns The federation object.
125
+ * @throws {TypeError} If benchmark mode and `meterProvider` are both
126
+ * specified.
127
+ */
120
128
  async build(options) {
121
129
  const { FederationImpl } = await Promise.resolve().then(() => middleware_exports);
122
130
  const f = new FederationImpl(options);
@@ -3072,36 +3080,6 @@ function handleNodeInfoJrd(_request, context) {
3072
3080
  return Promise.resolve(response);
3073
3081
  }
3074
3082
  //#endregion
3075
- //#region src/federation/retry.ts
3076
- /**
3077
- * Creates an exponential backoff retry policy. The delay between retries
3078
- * starts at the `initialDelay` and is multiplied by the `factor` for each
3079
- * subsequent retry, up to the `maxDelay`. The policy will give up after
3080
- * `maxAttempts` attempts. The actual delay is randomized to avoid
3081
- * synchronization (jitter).
3082
- * @param options The options for the policy.
3083
- * @returns The retry policy.
3084
- * @since 0.12.0
3085
- */
3086
- function createExponentialBackoffPolicy(options = {}) {
3087
- const initialDelay = Temporal.Duration.from(options.initialDelay ?? { seconds: 1 });
3088
- const maxDelay = Temporal.Duration.from(options.maxDelay ?? { hours: 12 });
3089
- const maxAttempts = options.maxAttempts ?? 10;
3090
- const factor = options.factor ?? 2;
3091
- const jitter = options.jitter ?? true;
3092
- return ({ attempts }) => {
3093
- if (attempts >= maxAttempts) return null;
3094
- let milliseconds = initialDelay.total("millisecond");
3095
- milliseconds *= factor ** attempts;
3096
- if (jitter) {
3097
- milliseconds *= 1 + Math.random();
3098
- milliseconds = Math.round(milliseconds);
3099
- }
3100
- const delay = Temporal.Duration.from({ milliseconds });
3101
- return Temporal.Duration.compare(delay, maxDelay) > 0 ? maxDelay : delay;
3102
- };
3103
- }
3104
- //#endregion
3105
3083
  //#region src/federation/send.ts
3106
3084
  /**
3107
3085
  * Extracts the inbox URLs from recipients.
@@ -3331,6 +3309,231 @@ var SendActivityError = class extends Error {
3331
3309
  }
3332
3310
  };
3333
3311
  //#endregion
3312
+ //#region src/federation/bench.ts
3313
+ /**
3314
+ * Metric reader owned by `benchmarkMode`.
3315
+ * @since 2.3.0
3316
+ */
3317
+ var BenchmarkMetricReader = class extends MetricReader {
3318
+ onShutdown() {
3319
+ return Promise.resolve();
3320
+ }
3321
+ onForceFlush() {
3322
+ return Promise.resolve();
3323
+ }
3324
+ };
3325
+ /**
3326
+ * Creates the in-process OpenTelemetry meter provider used by benchmark mode.
3327
+ * @returns The meter provider and the metric reader attached to it.
3328
+ * @since 2.3.0
3329
+ */
3330
+ function createBenchmarkMeterProvider() {
3331
+ const reader = new BenchmarkMetricReader();
3332
+ return {
3333
+ meterProvider: new MeterProvider$1({ readers: [reader] }),
3334
+ reader
3335
+ };
3336
+ }
3337
+ /**
3338
+ * Collects and serializes benchmark-mode metrics from a benchmark reader.
3339
+ * @param reader The benchmark metric reader to collect from.
3340
+ * @returns A server metric snapshot with any collection errors stringified.
3341
+ * @since 2.3.0
3342
+ */
3343
+ async function collectBenchmarkMetrics(reader) {
3344
+ const result = await reader.collect();
3345
+ return {
3346
+ version: 1,
3347
+ source: "server",
3348
+ generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
3349
+ scopeMetrics: serializeScopeMetrics(result.resourceMetrics),
3350
+ errors: result.errors.map((error) => String(error))
3351
+ };
3352
+ }
3353
+ /**
3354
+ * Handles `GET /.well-known/fedify/bench/stats`.
3355
+ * @param request The HTTP request to handle.
3356
+ * @param reader The benchmark metric reader to collect from.
3357
+ * @returns A JSON metric snapshot response, or `405 Method Not Allowed`.
3358
+ * @since 2.3.0
3359
+ */
3360
+ async function handleBenchmarkStats(request, reader) {
3361
+ if (request.method !== "GET") return new Response("Method not allowed", {
3362
+ status: 405,
3363
+ headers: { "Allow": "GET" }
3364
+ });
3365
+ return jsonResponse(await collectBenchmarkMetrics(reader));
3366
+ }
3367
+ /**
3368
+ * Handles `POST /.well-known/fedify/bench/trigger`.
3369
+ *
3370
+ * The handler validates a benchmark trigger request, checks recipients against
3371
+ * server-controlled trigger options, and calls `Context.sendActivity()` to use
3372
+ * the target's normal outbox path.
3373
+ * @param request The HTTP request to handle.
3374
+ * @param context The Fedify context used to resolve actors and send activity.
3375
+ * @param options Server-controlled benchmark trigger delivery options.
3376
+ * @returns A JSON response describing the sent activity, or a validation error.
3377
+ * @since 2.3.0
3378
+ */
3379
+ async function handleBenchmarkTrigger(request, context, options = {}) {
3380
+ if (request.method !== "POST") return new Response("Method not allowed", {
3381
+ status: 405,
3382
+ headers: { "Allow": "POST" }
3383
+ });
3384
+ let json;
3385
+ try {
3386
+ json = await request.json();
3387
+ } catch {
3388
+ return jsonResponse({ error: "Invalid JSON request body." }, 400);
3389
+ }
3390
+ try {
3391
+ const body = asRecord(json, "request body");
3392
+ const sender = parseSender(body.sender);
3393
+ const recipients = await parseRecipients(body.recipients, context);
3394
+ const activity = await parseActivity(body.activity, context);
3395
+ if (activity.id == null) throw new BenchmarkTriggerError("activity must have an id.");
3396
+ const activityId = activity.id.href;
3397
+ const inboxes = extractInboxes({ recipients });
3398
+ const inboxUrls = Object.keys(inboxes);
3399
+ if (inboxUrls.length < 1) throw new BenchmarkTriggerError("No valid recipient inboxes found. The recipients list must not be empty.");
3400
+ const unsafeInboxes = options.allowUnsafeRecipients ? [] : inboxUrls.filter((inbox) => !options.sinks?.has(inbox));
3401
+ if (unsafeInboxes.length > 0) return jsonResponse({
3402
+ error: "unsafe_recipient",
3403
+ unsafeInboxes
3404
+ }, 403);
3405
+ await context.sendActivity(sender, recipients, activity);
3406
+ return jsonResponse({
3407
+ version: 1,
3408
+ activityId,
3409
+ queueCorrelationId: activityId,
3410
+ recipientCount: recipients.length,
3411
+ inboxCount: inboxUrls.length
3412
+ }, 202);
3413
+ } catch (error) {
3414
+ if (error instanceof BenchmarkTriggerError) return jsonResponse({ error: error.message }, error.status);
3415
+ throw error;
3416
+ }
3417
+ }
3418
+ var BenchmarkTriggerError = class extends Error {
3419
+ status;
3420
+ constructor(message, status = 400) {
3421
+ super(message);
3422
+ this.status = status;
3423
+ }
3424
+ };
3425
+ function parseSender(value) {
3426
+ const sender = asRecord(value, "sender");
3427
+ if (typeof sender.identifier === "string") return { identifier: sender.identifier };
3428
+ if (typeof sender.username === "string") return { username: sender.username };
3429
+ throw new BenchmarkTriggerError("sender must be { identifier } or { username }.");
3430
+ }
3431
+ async function parseRecipients(value, context) {
3432
+ if (!Array.isArray(value)) throw new BenchmarkTriggerError("recipients must be an array.");
3433
+ return await Promise.all(value.map(async (item) => {
3434
+ let object;
3435
+ try {
3436
+ object = await Object$1.fromJsonLd(item, {
3437
+ documentLoader: context.documentLoader,
3438
+ contextLoader: context.contextLoader
3439
+ });
3440
+ } catch (error) {
3441
+ throw new BenchmarkTriggerError(`Invalid ActivityPub recipient: ${error}`);
3442
+ }
3443
+ if (!isRecipient(object)) throw new BenchmarkTriggerError("each recipient must be an ActivityPub actor.");
3444
+ const recipient = object;
3445
+ if (recipient.id == null || recipient.inboxId == null) throw new BenchmarkTriggerError("each recipient must have id and inbox properties.");
3446
+ return recipient;
3447
+ }));
3448
+ }
3449
+ function isRecipient(value) {
3450
+ return value != null && typeof value === "object" && "id" in value && "inboxId" in value;
3451
+ }
3452
+ async function parseActivity(value, context) {
3453
+ try {
3454
+ return await Activity.fromJsonLd(value, {
3455
+ documentLoader: context.documentLoader,
3456
+ contextLoader: context.contextLoader
3457
+ });
3458
+ } catch (error) {
3459
+ throw new BenchmarkTriggerError(`Invalid ActivityPub activity: ${error}`);
3460
+ }
3461
+ }
3462
+ function asRecord(value, name) {
3463
+ if (value == null || typeof value !== "object" || Array.isArray(value)) throw new BenchmarkTriggerError(`${name} must be an object.`);
3464
+ return value;
3465
+ }
3466
+ function jsonResponse(body, status = 200) {
3467
+ return new Response(JSON.stringify(body), {
3468
+ status,
3469
+ headers: { "Content-Type": "application/json" }
3470
+ });
3471
+ }
3472
+ function serializeScopeMetrics(resourceMetrics) {
3473
+ return resourceMetrics.scopeMetrics.map(serializeScope);
3474
+ }
3475
+ function serializeScope(scopeMetrics) {
3476
+ return {
3477
+ scope: {
3478
+ name: scopeMetrics.scope.name,
3479
+ version: scopeMetrics.scope.version
3480
+ },
3481
+ metrics: scopeMetrics.metrics.map(serializeMetric)
3482
+ };
3483
+ }
3484
+ function serializeMetric(metric) {
3485
+ return {
3486
+ name: metric.descriptor.name,
3487
+ description: metric.descriptor.description,
3488
+ unit: metric.descriptor.unit,
3489
+ dataPointType: serializeDataPointType(metric.dataPointType),
3490
+ dataPoints: metric.dataPoints.map((point) => ({
3491
+ attributes: { ...point.attributes },
3492
+ startTime: point.startTime,
3493
+ endTime: point.endTime,
3494
+ value: point.value
3495
+ }))
3496
+ };
3497
+ }
3498
+ function serializeDataPointType(dataPointType) {
3499
+ switch (dataPointType) {
3500
+ case DataPointType.HISTOGRAM: return "histogram";
3501
+ case DataPointType.EXPONENTIAL_HISTOGRAM: return "exponential_histogram";
3502
+ case DataPointType.GAUGE: return "gauge";
3503
+ case DataPointType.SUM: return "sum";
3504
+ }
3505
+ }
3506
+ //#endregion
3507
+ //#region src/federation/retry.ts
3508
+ /**
3509
+ * Creates an exponential backoff retry policy. The delay between retries
3510
+ * starts at the `initialDelay` and is multiplied by the `factor` for each
3511
+ * subsequent retry, up to the `maxDelay`. The policy will give up after
3512
+ * `maxAttempts` attempts. The actual delay is randomized to avoid
3513
+ * synchronization (jitter).
3514
+ * @param options The options for the policy.
3515
+ * @returns The retry policy.
3516
+ * @since 0.12.0
3517
+ */
3518
+ function createExponentialBackoffPolicy(options = {}) {
3519
+ const initialDelay = Temporal.Duration.from(options.initialDelay ?? { seconds: 1 });
3520
+ const maxDelay = Temporal.Duration.from(options.maxDelay ?? { hours: 12 });
3521
+ const maxAttempts = options.maxAttempts ?? 10;
3522
+ const factor = options.factor ?? 2;
3523
+ const jitter = options.jitter ?? true;
3524
+ return ({ attempts }) => {
3525
+ if (attempts >= maxAttempts) return null;
3526
+ let milliseconds = initialDelay.total("millisecond");
3527
+ milliseconds *= factor ** attempts;
3528
+ if (jitter) {
3529
+ milliseconds *= 1 + Math.random();
3530
+ milliseconds = Math.round(milliseconds);
3531
+ }
3532
+ const delay = Temporal.Duration.from({ milliseconds });
3533
+ return Temporal.Duration.compare(delay, maxDelay) > 0 ? maxDelay : delay;
3534
+ };
3535
+ }
3536
+ //#endregion
3334
3537
  //#region src/federation/webfinger.ts
3335
3538
  const logger = getLogger([
3336
3539
  "fedify",
@@ -3527,6 +3730,7 @@ var middleware_exports = /* @__PURE__ */ __exportAll({
3527
3730
  createFederation: () => createFederation
3528
3731
  });
3529
3732
  const circuitBreakerCasWarningKvStores = /* @__PURE__ */ new WeakSet();
3733
+ let nextQueueDepthGaugeSourceId = 0;
3530
3734
  const retryAfterHttpDate = /* @__PURE__ */ new RegExp("^(?:(?:Mon|Tue|Wed|Thu|Fri|Sat|Sun), \\d{2} (?:Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec) \\d{4} \\d{2}:\\d{2}:\\d{2} GMT|(?:Monday|Tuesday|Wednesday|Thursday|Friday|Saturday|Sunday), \\d{2}-(?:Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)-\\d{2} \\d{2}:\\d{2}:\\d{2} GMT|(?:Mon|Tue|Wed|Thu|Fri|Sat|Sun) (?:Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec) (?: \\d|\\d{2}) \\d{2}:\\d{2}:\\d{2} \\d{4})$");
3531
3735
  function parseRetryAfter(headers, now = Temporal.Now.instant()) {
3532
3736
  const value = headers.get("Retry-After");
@@ -3555,6 +3759,54 @@ function parseRetryAfterDuration(durationLike) {
3555
3759
  function clampNegativeDelay(delay) {
3556
3760
  return delay.sign < 0 ? Temporal.Duration.from({ seconds: 0 }) : delay;
3557
3761
  }
3762
+ function getBenchmarkRelaxations(allowPrivateAddress, signatureTimeWindow) {
3763
+ const relaxations = [];
3764
+ if (allowPrivateAddress) relaxations.push({
3765
+ protection: "private_address_checks",
3766
+ effect: "disabled",
3767
+ effectiveValue: true
3768
+ });
3769
+ if (signatureTimeWindow === false) relaxations.push({
3770
+ protection: "http_signature_time_window",
3771
+ effect: "disabled",
3772
+ effectiveValue: false,
3773
+ secureDefaultSeconds: 3600
3774
+ });
3775
+ else try {
3776
+ const seconds = Temporal.Duration.from(signatureTimeWindow).total({ unit: "seconds" });
3777
+ if (seconds !== 3600) relaxations.push({
3778
+ protection: "http_signature_time_window",
3779
+ effect: "changed",
3780
+ effectiveSeconds: seconds,
3781
+ secureDefaultSeconds: 3600
3782
+ });
3783
+ } catch {}
3784
+ return relaxations;
3785
+ }
3786
+ function formatBenchmarkRelaxations(relaxations) {
3787
+ if (relaxations.length < 1) return "no benchmark-only protections relaxed";
3788
+ return relaxations.map((relaxation) => {
3789
+ switch (relaxation.protection) {
3790
+ case "private_address_checks": return "private address checks disabled (allowPrivateAddress=true)";
3791
+ case "http_signature_time_window":
3792
+ if (relaxation.effect === "disabled") return `HTTP Signature time window disabled (signatureTimeWindow=false)`;
3793
+ return `HTTP Signature time window set to ${relaxation.effectiveSeconds}s (secure default: ${relaxation.secureDefaultSeconds}s)`;
3794
+ }
3795
+ }).join("; ");
3796
+ }
3797
+ function getBenchmarkTriggerOptions(benchmarkOptions) {
3798
+ const sinks = benchmarkOptions.triggerSinks?.map((sink) => {
3799
+ try {
3800
+ return new URL(sink).href;
3801
+ } catch {
3802
+ throw new TypeError("benchmarkMode.triggerSinks must contain only URLs.");
3803
+ }
3804
+ });
3805
+ return {
3806
+ sinks: sinks == null ? void 0 : new Set(sinks),
3807
+ allowUnsafeRecipients: benchmarkOptions.allowUnsafeTriggerRecipients === true
3808
+ };
3809
+ }
3558
3810
  function maxDelay(first, second) {
3559
3811
  return Temporal.Duration.compare(first, second) >= 0 ? first : second;
3560
3812
  }
@@ -3591,8 +3843,10 @@ function isPermanentInboxParseError(error) {
3591
3843
  }
3592
3844
  /**
3593
3845
  * Create a new {@link Federation} instance.
3594
- * @param parameters Parameters for initializing the instance.
3846
+ * @param options Parameters for initializing the instance.
3595
3847
  * @returns A new {@link Federation} instance.
3848
+ * @throws {TypeError} If benchmark mode and `meterProvider` are both
3849
+ * specified.
3596
3850
  * @since 0.10.0
3597
3851
  */
3598
3852
  function createFederation(options) {
@@ -3626,8 +3880,31 @@ var FederationImpl = class extends FederationBuilderImpl {
3626
3880
  _meterProvider;
3627
3881
  firstKnock;
3628
3882
  inboxChallengePolicy;
3883
+ benchmarkMode;
3884
+ benchmarkMetricReader;
3885
+ benchmarkTriggerOptions;
3886
+ #queueDepthGaugeSourceId = `fedify-${(++nextQueueDepthGaugeSourceId).toString(36)}`;
3887
+ #queueDepthGaugeEntries = [];
3888
+ #queueDepthGaugeMeterProvider;
3629
3889
  constructor(options) {
3630
3890
  super();
3891
+ const benchmarkMode = options.benchmarkMode != null && options.benchmarkMode !== false;
3892
+ const benchmarkOptions = typeof options.benchmarkMode === "object" ? options.benchmarkMode : {};
3893
+ const hasCustomLoaderFactory = options.documentLoaderFactory != null || options.contextLoaderFactory != null;
3894
+ const allowPrivateAddress = options.allowPrivateAddress ?? (benchmarkMode && !hasCustomLoaderFactory ? true : false);
3895
+ const signatureTimeWindow = options.signatureTimeWindow ?? (benchmarkMode ? false : { hours: 1 });
3896
+ if (benchmarkMode && options.meterProvider != null) throw new TypeError("benchmarkMode requires Fedify to own the meterProvider; OpenTelemetry metric readers cannot be added after a MeterProvider is constructed.");
3897
+ if (benchmarkMode) {
3898
+ const relaxations = getBenchmarkRelaxations(allowPrivateAddress, signatureTimeWindow);
3899
+ const relaxationSummary = formatBenchmarkRelaxations(relaxations);
3900
+ getLogger([
3901
+ "fedify",
3902
+ "federation",
3903
+ "benchmark"
3904
+ ]).warn(`Fedify benchmarkMode is enabled; ${relaxationSummary}. Benchmark endpoints are active and must not be used in production.`, { relaxations });
3905
+ }
3906
+ this.benchmarkMode = benchmarkMode;
3907
+ this.benchmarkTriggerOptions = benchmarkMode ? getBenchmarkTriggerOptions(benchmarkOptions) : {};
3631
3908
  this.kv = options.kv;
3632
3909
  this.kvPrefixes = {
3633
3910
  activityIdempotence: ["_fedify", "activityIdempotence"],
@@ -3695,13 +3972,13 @@ var FederationImpl = class extends FederationBuilderImpl {
3695
3972
  }
3696
3973
  this.router.trailingSlashInsensitive = options.trailingSlashInsensitive ?? false;
3697
3974
  this._initializeRouter();
3698
- if (options.allowPrivateAddress || options.userAgent != null) {
3975
+ if (options.allowPrivateAddress === true || options.userAgent != null) {
3699
3976
  if (options.documentLoaderFactory != null) throw new TypeError("Cannot set documentLoaderFactory with allowPrivateAddress or userAgent options.");
3700
3977
  if (options.contextLoaderFactory != null) throw new TypeError("Cannot set contextLoaderFactory with allowPrivateAddress or userAgent options.");
3701
3978
  if (options.authenticatedDocumentLoaderFactory != null) throw new TypeError("Cannot set authenticatedDocumentLoaderFactory with allowPrivateAddress or userAgent options.");
3702
3979
  }
3703
- const { allowPrivateAddress, userAgent } = options;
3704
- this.allowPrivateAddress = allowPrivateAddress ?? false;
3980
+ const { userAgent } = options;
3981
+ this.allowPrivateAddress = allowPrivateAddress;
3705
3982
  const userDocumentLoaderFactory = options.documentLoaderFactory;
3706
3983
  const userContextLoaderFactory = options.contextLoaderFactory;
3707
3984
  const userAuthFactory = options.authenticatedDocumentLoaderFactory;
@@ -3749,25 +4026,55 @@ var FederationImpl = class extends FederationBuilderImpl {
3749
4026
  this.userAgent = userAgent;
3750
4027
  this.onOutboxError = options.onOutboxError;
3751
4028
  this.permanentFailureStatusCodes = options.permanentFailureStatusCodes ?? [404, 410];
3752
- this.signatureTimeWindow = options.signatureTimeWindow ?? { hours: 1 };
4029
+ this.signatureTimeWindow = signatureTimeWindow;
3753
4030
  this.skipSignatureVerification = options.skipSignatureVerification ?? false;
3754
4031
  this.inboxChallengePolicy = options.inboxChallengePolicy;
3755
4032
  this.outboxRetryPolicy = options.outboxRetryPolicy ?? createExponentialBackoffPolicy();
3756
4033
  this.inboxRetryPolicy = options.inboxRetryPolicy ?? createExponentialBackoffPolicy();
3757
4034
  this.activityTransformers = options.activityTransformers ?? getDefaultActivityTransformers();
3758
4035
  this._tracerProvider = options.tracerProvider;
3759
- this._meterProvider = options.meterProvider;
4036
+ if (benchmarkMode) {
4037
+ const benchmarkMetrics = createBenchmarkMeterProvider();
4038
+ this._meterProvider = benchmarkMetrics.meterProvider;
4039
+ this.benchmarkMetricReader = benchmarkMetrics.reader;
4040
+ } else this._meterProvider = options.meterProvider;
4041
+ this.#queueDepthGaugeEntries = [
4042
+ {
4043
+ role: "inbox",
4044
+ queue: this.inboxQueue
4045
+ },
4046
+ {
4047
+ role: "outbox",
4048
+ queue: this.outboxQueue
4049
+ },
4050
+ {
4051
+ role: "fanout",
4052
+ queue: this.fanoutQueue
4053
+ }
4054
+ ];
4055
+ this.#registerQueueDepthGauge(this._meterProvider ?? metrics.getMeterProvider());
3760
4056
  this.firstKnock = options.firstKnock;
3761
4057
  }
3762
4058
  get tracerProvider() {
3763
4059
  return this._tracerProvider ?? trace.getTracerProvider();
3764
4060
  }
3765
4061
  get meterProvider() {
3766
- return this._meterProvider ?? metrics.getMeterProvider();
4062
+ const meterProvider = this._meterProvider ?? metrics.getMeterProvider();
4063
+ this.#registerQueueDepthGauge(meterProvider);
4064
+ return meterProvider;
4065
+ }
4066
+ #registerQueueDepthGauge(meterProvider) {
4067
+ if (meterProvider === this.#queueDepthGaugeMeterProvider) return;
4068
+ registerQueueDepthGauge(meterProvider, this.#queueDepthGaugeEntries, { sourceId: this.#queueDepthGaugeSourceId });
4069
+ this.#queueDepthGaugeMeterProvider = meterProvider;
3767
4070
  }
3768
4071
  _initializeRouter() {
3769
4072
  this.router.add("/.well-known/webfinger", "webfinger");
3770
4073
  this.router.add("/.well-known/nodeinfo", "nodeInfoJrd");
4074
+ if (this.benchmarkMode) {
4075
+ this.router.add("/.well-known/fedify/bench/stats", "benchmarkStats");
4076
+ this.router.add("/.well-known/fedify/bench/trigger", "benchmarkTrigger");
4077
+ }
3771
4078
  }
3772
4079
  _getTracer() {
3773
4080
  return this.tracerProvider.getTracer(name, version);
@@ -4779,6 +5086,8 @@ var FederationImpl = class extends FederationBuilderImpl {
4779
5086
  context,
4780
5087
  nodeInfoDispatcher: this.nodeInfoDispatcher
4781
5088
  });
5089
+ case "benchmarkStats": return await handleBenchmarkStats(request, this.benchmarkMetricReader);
5090
+ case "benchmarkTrigger": return await handleBenchmarkTrigger(request, context, this.benchmarkTriggerOptions);
4782
5091
  }
4783
5092
  if (request.method !== "POST" && !acceptsJsonLd(request)) {
4784
5093
  metricState.endpoint = "not_acceptable";
@@ -5018,6 +5327,8 @@ function getEndpointCategory(routeName) {
5018
5327
  case "liked": return "liked";
5019
5328
  case "featured": return "featured";
5020
5329
  case "featuredTags": return "featured_tags";
5330
+ case "benchmarkStats":
5331
+ case "benchmarkTrigger": return "benchmark";
5021
5332
  default: return "not_found";
5022
5333
  }
5023
5334
  }
@@ -6088,4 +6399,4 @@ function getRequestId(request) {
6088
6399
  return `req_${Date.now().toString(36)}${Math.random().toString(36).slice(2, 8)}`;
6089
6400
  }
6090
6401
  //#endregion
6091
- export { createExponentialBackoffPolicy as a, buildCollectionSynchronizationHeader as c, normalizeCircuitBreakerOptions as d, parseCircuitBreakerKvState as f, SendActivityError as i, digest as l, middleware_exports as n, respondWithObject as o, createFederationBuilder as p, handleWebFinger as r, respondWithObjectIfAcceptable as s, createFederation as t, CircuitBreaker as u };
6402
+ export { SendActivityError as a, buildCollectionSynchronizationHeader as c, normalizeCircuitBreakerOptions as d, parseCircuitBreakerKvState as f, createExponentialBackoffPolicy as i, digest as l, middleware_exports as n, respondWithObject as o, createFederationBuilder as p, handleWebFinger as r, respondWithObjectIfAcceptable as s, createFederation as t, CircuitBreaker as u };