@fedify/fedify 2.3.0-dev.994 → 2.3.0-pr.809.37

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 (138) hide show
  1. package/README.md +16 -7
  2. package/dist/{assert-DikXweDx.mjs → assert-OguE97r2.mjs} +1 -1
  3. package/dist/{assert_instance_of-C4Ri6VuN.mjs → assert_instance_of-DBC5X09g.mjs} +1 -1
  4. package/dist/{assert_not_equals--wG9hV7u.mjs → assert_not_equals-DkVK8oqV.mjs} +1 -1
  5. package/dist/{assert_rejects-B-qJtC9Z.mjs → assert_rejects-DN60FHPX.mjs} +28 -3
  6. package/dist/{assert_strict_equals-Dmjbg-bA.mjs → assert_strict_equals-XEgZAlrj.mjs} +1 -1
  7. package/dist/{assert_throws-4NwKEy2q.mjs → assert_throws-BOkhLGYc.mjs} +1 -1
  8. package/dist/{builder-yAlpiIqP.mjs → builder-Dl5Sdmc9.mjs} +96 -42
  9. package/dist/{chunk-nlSIicah.js → chunk-CRNNMoPX.js} +2 -2
  10. package/dist/circuit-breaker-CSWsyoef.mjs +337 -0
  11. package/dist/{client-z-8dc-e1.d.cts → client-CAM_bQXx.d.cts} +1 -0
  12. package/dist/{client-AtlibPOU.d.ts → client-CSddvgWN.d.ts} +1 -2
  13. package/dist/compat/mod.d.cts +2 -1
  14. package/dist/compat/mod.d.ts +2 -3
  15. package/dist/compat/mod.js +3 -3
  16. package/dist/compat/outgoing-jsonld.test.mjs +4 -4
  17. package/dist/compat/public-audience.test.mjs +4 -4
  18. package/dist/compat/transformers.test.mjs +5 -5
  19. package/dist/{context-BzH2-ajs.d.ts → context-BBVLF7lx.d.cts} +342 -269
  20. package/dist/{context-DJGagtNd.d.cts → context-BU6jSQdo.d.ts} +344 -266
  21. package/dist/{context-Dk_tacqz.mjs → context-DVoTs_wM.mjs} +64 -5
  22. package/dist/{deno-XBVlKnOX.mjs → deno-BID9qTPk.mjs} +1 -1
  23. package/dist/{docloader-DPp-X6gk.mjs → docloader-BYFuVwwi.mjs} +4 -4
  24. package/dist/{esm-DVILvP5e.mjs → esm-vrlUxr60.mjs} +4 -7
  25. package/dist/federation/builder.test.mjs +163 -11
  26. package/dist/federation/circuit-breaker.test.d.mts +2 -0
  27. package/dist/federation/circuit-breaker.test.mjs +446 -0
  28. package/dist/federation/collection.test.mjs +3 -3
  29. package/dist/federation/handler.test.mjs +1770 -29
  30. package/dist/federation/idempotency.test.mjs +5 -5
  31. package/dist/federation/inbox.test.mjs +4 -4
  32. package/dist/federation/keycache.test.mjs +5 -5
  33. package/dist/federation/kv.test.mjs +2 -2
  34. package/dist/federation/metrics.test.d.mts +2 -0
  35. package/dist/federation/metrics.test.mjs +634 -0
  36. package/dist/federation/middleware.test.mjs +4036 -146
  37. package/dist/federation/mod.cjs +195 -14
  38. package/dist/federation/mod.d.cts +6 -4
  39. package/dist/federation/mod.d.ts +6 -6
  40. package/dist/federation/mod.js +192 -14
  41. package/dist/federation/mq.test.mjs +176 -15
  42. package/dist/federation/negotiation.test.mjs +4 -4
  43. package/dist/federation/retry.test.mjs +3 -3
  44. package/dist/federation/router.test.mjs +190 -9
  45. package/dist/federation/send.test.mjs +153 -15
  46. package/dist/federation/temporal.test.d.mts +2 -0
  47. package/dist/federation/temporal.test.mjs +71 -0
  48. package/dist/federation/webfinger.test.mjs +150 -5
  49. package/dist/{http-B4rrzxtg.js → http-B47Jg9iX.js} +994 -64
  50. package/dist/{http-CSlLA54v.mjs → http-CO59u1Yk.mjs} +146 -47
  51. package/dist/{http-BrGqJDXL.cjs → http-CjZwUPiT.cjs} +1099 -61
  52. package/dist/{http-aQzN9Ayi.d.ts → http-VyDTd4G3.d.cts} +15 -3
  53. package/dist/{http-CrGuipxe.d.cts → http-lf8Hsd91.d.ts} +15 -1
  54. package/dist/{key-Bzx--sHD.mjs → key-Dqdlo3mG.mjs} +43 -18
  55. package/dist/{kv-CbLNp3zQ.d.cts → kv-D6hNiMTK.d.ts} +1 -0
  56. package/dist/{kv-cache-DI-Sk7-I.cjs → kv-cache-BQ1-zDKX.cjs} +20 -3
  57. package/dist/{kv-cache-EMIqoIuB.js → kv-cache-Dh2ZH9Gr.js} +20 -3
  58. package/dist/{kv-cache-U__xU4qR.mjs → kv-cache-DhLkLv0P.mjs} +21 -3
  59. package/dist/{kv-GFYnFoOl.d.ts → kv-gJ8LYbxX.d.cts} +1 -3
  60. package/dist/ld-JbCByP5P.mjs +626 -0
  61. package/dist/metrics-ColY3QKj.mjs +815 -0
  62. package/dist/{middleware-zjZ6AFCW.mjs → middleware-Buqzuiaf.mjs} +1 -1
  63. package/dist/{middleware-An4DjES4.cjs → middleware-CWQ3Yi07.cjs} +2318 -623
  64. package/dist/{middleware-K1g6bEkV.mjs → middleware-Cg4EyWsq.mjs} +1639 -373
  65. package/dist/{middleware-nidH7VZH.js → middleware-iAJY2aU-.js} +2260 -556
  66. package/dist/{mod-CR8soWa9.d.ts → mod-B0hW12_O.d.cts} +26 -4
  67. package/dist/mod-C0F6kvgS.d.cts +182 -0
  68. package/dist/{mod-Cr3f-ACa.d.cts → mod-COIAjwRS.d.ts} +26 -2
  69. package/dist/{mod-CMEbIaNh.d.cts → mod-DFvNJcNb.d.ts} +56 -4
  70. package/dist/mod-vPYVoa5n.d.ts +182 -0
  71. package/dist/{mod-CLgIXe9w.d.ts → mod-yvIXFAEi.d.cts} +56 -6
  72. package/dist/mod.cjs +9 -6
  73. package/dist/mod.d.cts +12 -10
  74. package/dist/mod.d.ts +12 -12
  75. package/dist/mod.js +11 -11
  76. package/dist/mq-D-nlpY04.d.ts +208 -0
  77. package/dist/mq-D8uSFzxe.d.cts +208 -0
  78. package/dist/{negotiation-SQvQgUqe.mjs → negotiation-DDstyBvc.mjs} +29 -0
  79. package/dist/nodeinfo/client.test.mjs +4 -4
  80. package/dist/nodeinfo/handler.test.mjs +4 -4
  81. package/dist/nodeinfo/mod.d.cts +2 -1
  82. package/dist/nodeinfo/mod.d.ts +2 -3
  83. package/dist/nodeinfo/mod.js +3 -3
  84. package/dist/nodeinfo/types.test.mjs +3 -3
  85. package/dist/otel/exporter.test.mjs +28 -25
  86. package/dist/otel/mod.cjs +6 -5
  87. package/dist/otel/mod.d.cts +5 -3
  88. package/dist/otel/mod.d.ts +5 -5
  89. package/dist/otel/mod.js +8 -7
  90. package/dist/{outgoing-jsonld-CNmZLixq.mjs → outgoing-jsonld-L_DbOaFe.mjs} +2 -2
  91. package/dist/{owner-C-tqfvxn.mjs → owner-B4LgOmzV.mjs} +2 -2
  92. package/dist/{owner-CptqhsOy.d.cts → owner-CnngXDNJ.d.ts} +2 -1
  93. package/dist/{owner-74ARJ5TL.d.ts → owner-DEvZuyOE.d.cts} +2 -3
  94. package/dist/{proof-BlmRPzrK.mjs → proof-BoSGOq9q.mjs} +26 -8
  95. package/dist/{proof-Bbbwf8_x.js → proof-CzhZuawt.js} +378 -15
  96. package/dist/{proof-BW89QMVB.cjs → proof-DvMI-3vq.cjs} +432 -15
  97. package/dist/runtime/mod.d.cts +1 -0
  98. package/dist/runtime/mod.d.ts +1 -2
  99. package/dist/runtime/mod.js +3 -3
  100. package/dist/{send-05pJLcb6.mjs → send-ByK1Q7DK.mjs} +72 -26
  101. package/dist/sig/http.test.mjs +356 -33
  102. package/dist/sig/key.test.mjs +103 -6
  103. package/dist/sig/ld.test.mjs +696 -7
  104. package/dist/sig/mod.cjs +2 -2
  105. package/dist/sig/mod.d.cts +4 -3
  106. package/dist/sig/mod.d.ts +4 -5
  107. package/dist/sig/mod.js +4 -4
  108. package/dist/sig/owner.test.mjs +6 -6
  109. package/dist/sig/proof.test.mjs +169 -8
  110. package/dist/{std__assert-CRDpx_HF.mjs → std__assert-BBjXFNOb.mjs} +5 -30
  111. package/dist/temporal-LpjyAjKb.mjs +95 -0
  112. package/dist/testing/mod.d.mts +110 -10
  113. package/dist/testing/mod.mjs +1 -2
  114. package/dist/{transformers-ve6e2xcg.js → transformers-BGMIq1cs.js} +2 -2
  115. package/dist/{types-hvL8ElAs.js → types-CAY3OdLq.js} +2 -2
  116. package/dist/utils/docloader.test.mjs +6 -6
  117. package/dist/utils/kv-cache.test.mjs +67 -2
  118. package/dist/utils/mod.cjs +1 -1
  119. package/dist/utils/mod.d.cts +2 -1
  120. package/dist/utils/mod.d.ts +2 -3
  121. package/dist/utils/mod.js +3 -3
  122. package/dist/vocab/cjs.test.mjs +1 -1
  123. package/dist/vocab/mod.d.cts +1 -0
  124. package/dist/vocab/mod.d.ts +1 -2
  125. package/dist/vocab/mod.js +2 -2
  126. package/package.json +16 -16
  127. package/dist/ld-DR78beiv.mjs +0 -279
  128. package/dist/middleware-BFBaR0mF.cjs +0 -4
  129. package/dist/mod-2d12ffz3.d.ts +0 -64
  130. package/dist/mod-D35TRn09.d.cts +0 -62
  131. package/dist/router-CrMLXoOr.mjs +0 -114
  132. package/dist/{activity-listener-ell7W1s9.mjs → activity-listener-tztVvlNb.mjs} +0 -0
  133. package/dist/{assert_equals-Ew3jOFa3.mjs → assert_equals-C-ZRDbaf.mjs} +0 -0
  134. package/dist/{client-D_1QpnWt.mjs → client-ByXmQhYD.mjs} +1 -1
  135. package/dist/{collection-D-HqUuA2.mjs → collection-Cc3DVAhE.mjs} +0 -0
  136. package/dist/{keycache-EGATflN-.mjs → keycache-BeU0LCII.mjs} +0 -0
  137. package/dist/{public-audience-DYFHzm_c.mjs → public-audience-Cvbr2Gzt.mjs} +1 -1
  138. /package/dist/{retry-bMXBL97A.mjs → retry-CXg_MBI-.mjs} +0 -0
@@ -1,19 +1,21 @@
1
1
  import "@js-temporal/polyfill";
2
2
  import "urlpattern-polyfill";
3
3
  globalThis.addEventListener = () => {};
4
- import { t as esm_default } from "../esm-DVILvP5e.mjs";
5
- import { t as assertEquals } from "../assert_equals-Ew3jOFa3.mjs";
6
- import "../std__assert-CRDpx_HF.mjs";
7
- import { n as assertFalse, t as assertRejects } from "../assert_rejects-B-qJtC9Z.mjs";
8
- import { t as assertInstanceOf } from "../assert_instance_of-C4Ri6VuN.mjs";
9
- import { t as assertNotEquals } from "../assert_not_equals--wG9hV7u.mjs";
10
- import { t as assert } from "../assert-DikXweDx.mjs";
11
- import { l as verifyRequest } from "../http-CSlLA54v.mjs";
4
+ import { t as esm_default } from "../esm-vrlUxr60.mjs";
5
+ import { t as assertEquals } from "../assert_equals-C-ZRDbaf.mjs";
6
+ import "../std__assert-BBjXFNOb.mjs";
7
+ import { n as assertGreaterOrEqual, r as assertFalse, t as assertRejects } from "../assert_rejects-DN60FHPX.mjs";
8
+ import { t as assertInstanceOf } from "../assert_instance_of-DBC5X09g.mjs";
9
+ import { t as assertNotEquals } from "../assert_not_equals-DkVK8oqV.mjs";
10
+ import { t as assert } from "../assert-OguE97r2.mjs";
11
+ import { l as verifyRequest } from "../http-CO59u1Yk.mjs";
12
12
  import { i as rsaPrivateKey2, n as ed25519PrivateKey, s as rsaPublicKey2, t as ed25519Multikey } from "../keys-DGu1NFwu.mjs";
13
- import { t as doesActorOwnKey } from "../owner-C-tqfvxn.mjs";
14
- import { n as extractInboxes, r as sendActivity, t as SendActivityError } from "../send-05pJLcb6.mjs";
15
- import { createTestTracerProvider, mockDocumentLoader, test } from "@fedify/fixture";
13
+ import { t as doesActorOwnKey } from "../owner-B4LgOmzV.mjs";
14
+ import { n as extractInboxes, r as sendActivity, t as SendActivityError } from "../send-ByK1Q7DK.mjs";
16
15
  import { Activity, Application, Endpoints, Group, Person, Service } from "@fedify/vocab";
16
+ import { createTestMeterProvider, createTestTracerProvider, mockDocumentLoader, test } from "@fedify/fixture";
17
+ import { FetchError } from "@fedify/vocab-runtime";
18
+ import { AggregationTemporality, InMemoryMetricExporter, MeterProvider, PeriodicExportingMetricReader } from "@opentelemetry/sdk-metrics";
17
19
  //#region src/federation/send.test.ts
18
20
  test("extractInboxes()", () => {
19
21
  const recipients = [
@@ -185,6 +187,7 @@ test("sendActivity()", async (t) => {
185
187
  });
186
188
  esm_default.post("https://example.com/inbox2", {
187
189
  status: 500,
190
+ headers: { "Retry-After": "120" },
188
191
  body: "something went wrong"
189
192
  });
190
193
  await t.step("failure", async () => {
@@ -227,8 +230,39 @@ test("sendActivity()", async (t) => {
227
230
  assertEquals(e.statusCode, 500);
228
231
  assertEquals(e.inbox, new URL("https://example.com/inbox2"));
229
232
  assertEquals(e.responseBody, "something went wrong");
233
+ assertEquals(e.responseHeaders.get("Retry-After"), "120");
230
234
  }
231
235
  });
236
+ await t.step("signed challenge retry transport errors throw FetchError", async () => {
237
+ const activity = {
238
+ "@context": "https://www.w3.org/ns/activitystreams",
239
+ "type": "Create",
240
+ "id": "https://example.com/activity",
241
+ "actor": "https://example.com/person"
242
+ };
243
+ const failure = /* @__PURE__ */ new TypeError("challenge retry connection reset");
244
+ let requestCount = 0;
245
+ esm_default.post("https://example.com/inbox-challenge-reset", () => {
246
+ requestCount++;
247
+ if (requestCount === 1) return new Response("Unauthorized", {
248
+ status: 401,
249
+ headers: { "Accept-Signature": "sig1=(\"@method\" \"@target-uri\" \"@authority\" \"content-digest\");created;nonce=\"retry-nonce\"" }
250
+ });
251
+ throw failure;
252
+ });
253
+ const error = await assertRejects(() => sendActivity({
254
+ activity,
255
+ activityId: "https://example.com/activity",
256
+ keys: [{
257
+ privateKey: rsaPrivateKey2,
258
+ keyId: rsaPublicKey2.id
259
+ }],
260
+ inbox: new URL("https://example.com/inbox-challenge-reset")
261
+ }), FetchError, "challenge retry connection reset");
262
+ assertEquals(error.url.href, "https://example.com/inbox-challenge-reset");
263
+ assertEquals(error.cause, failure);
264
+ assertEquals(requestCount, 2);
265
+ });
232
266
  esm_default.post("https://example.com/inbox-gone", {
233
267
  status: 410,
234
268
  body: "Gone"
@@ -407,13 +441,117 @@ test("sendActivity() records OpenTelemetry span events", async (t) => {
407
441
  assert(event.attributes != null);
408
442
  assertEquals(event.attributes["activitypub.inbox.url"], "https://example.com/inbox");
409
443
  assertEquals(event.attributes["activitypub.activity.id"], "https://example.com/activity");
410
- assert(typeof event.attributes["activitypub.activity.json"] === "string");
411
- const recordedActivity = JSON.parse(event.attributes["activitypub.activity.json"]);
412
- assertEquals(recordedActivity.id, "https://example.com/activity");
413
- assertEquals(recordedActivity.type, "Create");
444
+ assertEquals(event.attributes["activitypub.activity.type"], "https://www.w3.org/ns/activitystreams#Create");
445
+ assertEquals(event.attributes["activitypub.actor.id"], "https://example.com/person");
446
+ assertEquals(event.attributes["activitypub.activity.json"], void 0);
414
447
  exporter.clear();
415
448
  esm_default.hardReset();
416
449
  });
417
450
  });
451
+ test("sendActivity() records OpenTelemetry delivery metrics", async (t) => {
452
+ const [meterProvider, recorder] = createTestMeterProvider();
453
+ esm_default.spyGlobal();
454
+ await t.step("successful send", async () => {
455
+ esm_default.post("https://metrics.example:8443/inbox/path?x=1", { status: 202 });
456
+ await sendActivity({
457
+ activity: {
458
+ "@context": "https://www.w3.org/ns/activitystreams",
459
+ type: "Create",
460
+ id: "https://example.com/activity",
461
+ actor: "https://example.com/person"
462
+ },
463
+ activityId: "https://example.com/activity",
464
+ activityType: "https://www.w3.org/ns/activitystreams#Create",
465
+ keys: [],
466
+ inbox: new URL("https://metrics.example:8443/inbox/path?x=1"),
467
+ meterProvider
468
+ });
469
+ const sent = recorder.getMeasurements("activitypub.delivery.sent");
470
+ assertEquals(sent.length, 1);
471
+ assertEquals(sent[0].type, "counter");
472
+ assertEquals(sent[0].value, 1);
473
+ assertEquals(sent[0].attributes["activitypub.remote.host"], "metrics.example:8443");
474
+ assertEquals(sent[0].attributes["activitypub.activity.type"], "https://www.w3.org/ns/activitystreams#Create");
475
+ assertEquals(sent[0].attributes["activitypub.delivery.success"], true);
476
+ const durations = recorder.getMeasurements("activitypub.delivery.duration");
477
+ assertEquals(durations.length, 1);
478
+ assertEquals(durations[0].type, "histogram");
479
+ assertGreaterOrEqual(durations[0].value, 0);
480
+ assertEquals(durations[0].attributes["activitypub.remote.host"], "metrics.example:8443");
481
+ assertEquals(durations[0].attributes["activitypub.activity.type"], "https://www.w3.org/ns/activitystreams#Create");
482
+ assertEquals(durations[0].attributes["activitypub.delivery.success"], true);
483
+ recorder.clear();
484
+ esm_default.hardReset();
485
+ });
486
+ await t.step("failed HTTP response", async () => {
487
+ esm_default.spyGlobal();
488
+ esm_default.post("https://metrics.example/inbox", {
489
+ status: 500,
490
+ body: "failed"
491
+ });
492
+ const activity = {
493
+ "@context": "https://www.w3.org/ns/activitystreams",
494
+ type: "Follow",
495
+ id: "https://example.com/follow",
496
+ actor: "https://example.com/person"
497
+ };
498
+ await assertRejects(() => sendActivity({
499
+ activity,
500
+ activityId: "https://example.com/follow",
501
+ activityType: "https://www.w3.org/ns/activitystreams#Follow",
502
+ keys: [],
503
+ inbox: new URL("https://metrics.example/inbox"),
504
+ meterProvider
505
+ }), SendActivityError);
506
+ const sent = recorder.getMeasurements("activitypub.delivery.sent");
507
+ assertEquals(sent.length, 1);
508
+ assertEquals(sent[0].attributes["activitypub.delivery.success"], false);
509
+ assertEquals(sent[0].attributes["activitypub.activity.type"], "https://www.w3.org/ns/activitystreams#Follow");
510
+ assertEquals(sent[0].attributes["activitypub.remote.host"], "metrics.example");
511
+ const durations = recorder.getMeasurements("activitypub.delivery.duration");
512
+ assertEquals(durations.length, 1);
513
+ assertGreaterOrEqual(durations[0].value, 0);
514
+ assertEquals(durations[0].attributes["activitypub.remote.host"], "metrics.example");
515
+ assertEquals(durations[0].attributes["activitypub.activity.type"], "https://www.w3.org/ns/activitystreams#Follow");
516
+ assertEquals(durations[0].attributes["activitypub.delivery.success"], false);
517
+ recorder.clear();
518
+ esm_default.hardReset();
519
+ });
520
+ });
521
+ test("sendActivity() exports delivery metrics through OpenTelemetry SDK", async () => {
522
+ const exporter = new InMemoryMetricExporter(AggregationTemporality.CUMULATIVE);
523
+ const meterProvider = new MeterProvider({ readers: [new PeriodicExportingMetricReader({
524
+ exporter,
525
+ exportIntervalMillis: 6e4
526
+ })] });
527
+ esm_default.spyGlobal();
528
+ esm_default.post("https://sdk-metrics.example/inbox", { status: 202 });
529
+ try {
530
+ await sendActivity({
531
+ activity: {
532
+ "@context": "https://www.w3.org/ns/activitystreams",
533
+ type: "Create",
534
+ id: "https://example.com/activity",
535
+ actor: "https://example.com/person"
536
+ },
537
+ activityId: "https://example.com/activity",
538
+ activityType: "https://www.w3.org/ns/activitystreams#Create",
539
+ keys: [],
540
+ inbox: new URL("https://sdk-metrics.example/inbox"),
541
+ meterProvider
542
+ });
543
+ await meterProvider.forceFlush();
544
+ const sent = exporter.getMetrics().flatMap((resourceMetrics) => resourceMetrics.scopeMetrics).flatMap((scopeMetrics) => scopeMetrics.metrics).find((metric) => metric.descriptor.name === "activitypub.delivery.sent");
545
+ assert(sent != null);
546
+ assertEquals(sent.dataPoints.length, 1);
547
+ assertEquals(sent.dataPoints[0].attributes["activitypub.remote.host"], "sdk-metrics.example");
548
+ } finally {
549
+ try {
550
+ await meterProvider.shutdown();
551
+ } finally {
552
+ esm_default.hardReset();
553
+ }
554
+ }
555
+ });
418
556
  //#endregion
419
557
  export {};
@@ -0,0 +1,2 @@
1
+ import { Temporal } from "@js-temporal/polyfill";
2
+ import { URLPattern } from "urlpattern-polyfill";
@@ -0,0 +1,71 @@
1
+ import "@js-temporal/polyfill";
2
+ import "urlpattern-polyfill";
3
+ globalThis.addEventListener = () => {};
4
+ import "../std__assert-BBjXFNOb.mjs";
5
+ import { r as assertFalse } from "../assert_rejects-DN60FHPX.mjs";
6
+ import { t as assert } from "../assert-OguE97r2.mjs";
7
+ import { t as hasMalformedKnownTemporalLiteral } from "../temporal-LpjyAjKb.mjs";
8
+ import { test } from "@fedify/fixture";
9
+ //#region src/federation/temporal.test.ts
10
+ test("hasMalformedKnownTemporalLiteral() detects expanded proof timestamps", async () => {
11
+ assert(await hasMalformedKnownTemporalLiteral({
12
+ "@context": ["https://www.w3.org/ns/activitystreams", "https://w3id.org/security/data-integrity/v1"],
13
+ id: "https://example.com/activities/invalid-proof-created",
14
+ type: "Create",
15
+ actor: "https://example.com/person2",
16
+ object: {
17
+ id: "https://example.com/notes/invalid-proof-created",
18
+ type: "Note",
19
+ attributedTo: "https://example.com/person2",
20
+ content: "Hello, world!"
21
+ },
22
+ proof: {
23
+ type: "DataIntegrityProof",
24
+ cryptosuite: "eddsa-jcs-2022",
25
+ verificationMethod: "https://example.com/person2#main-key",
26
+ proofPurpose: "assertionMethod",
27
+ created: { "@value": "not-a-date" },
28
+ proofValue: "zLaewdp4H9kqtwyrLatK4cjY5oRHwVcw4gibPSUDYDMhi4M49v8pcYk3ZB6D69dNpAPbUmY8ocuJ3m9KhKJEEg7z"
29
+ }
30
+ }, void 0));
31
+ });
32
+ test("hasMalformedKnownTemporalLiteral() follows aliases in nested objects", async () => {
33
+ assert(await hasMalformedKnownTemporalLiteral({
34
+ "@context": ["https://www.w3.org/ns/activitystreams", { publishedAt: "as:published" }],
35
+ id: "https://example.com/activities/invalid-nested-published",
36
+ type: "Create",
37
+ actor: "https://example.com/person2",
38
+ object: {
39
+ id: "https://example.com/notes/invalid-nested-published",
40
+ type: "Note",
41
+ attributedTo: "https://example.com/person2",
42
+ content: "Hello, world!"
43
+ },
44
+ audience: {
45
+ type: "Note",
46
+ publishedAt: { "@value": "not-a-date" }
47
+ }
48
+ }, void 0));
49
+ });
50
+ test("hasMalformedKnownTemporalLiteral() does not over-classify ignored as:closed values", async () => {
51
+ assertFalse(await hasMalformedKnownTemporalLiteral({
52
+ "@context": "https://www.w3.org/ns/activitystreams",
53
+ type: "Question",
54
+ closed: "not-a-date"
55
+ }, void 0));
56
+ });
57
+ test("hasMalformedKnownTemporalLiteral() detects date-like invalid as:closed values", async () => {
58
+ assert(await hasMalformedKnownTemporalLiteral({
59
+ "@context": "https://www.w3.org/ns/activitystreams",
60
+ type: "Question",
61
+ closed: "2024-02-31T00:00:00Z"
62
+ }, void 0));
63
+ });
64
+ test("hasMalformedKnownTemporalLiteral() ignores custom-typed as:closed values", async () => {
65
+ assertFalse(await hasMalformedKnownTemporalLiteral({ "https://www.w3.org/ns/activitystreams#closed": [{
66
+ "@value": "2024-02-31T00:00:00Z",
67
+ "@type": "https://example.com/ns#customDateTime"
68
+ }] }, void 0));
69
+ });
70
+ //#endregion
71
+ export {};
@@ -1,13 +1,14 @@
1
1
  import { Temporal } from "@js-temporal/polyfill";
2
2
  import "urlpattern-polyfill";
3
3
  globalThis.addEventListener = () => {};
4
- import { r as createRequestContext } from "../context-Dk_tacqz.mjs";
5
- import { t as assertEquals } from "../assert_equals-Ew3jOFa3.mjs";
6
- import "../std__assert-CRDpx_HF.mjs";
4
+ import { r as createRequestContext } from "../context-DVoTs_wM.mjs";
5
+ import { t as assertEquals } from "../assert_equals-C-ZRDbaf.mjs";
6
+ import "../std__assert-BBjXFNOb.mjs";
7
+ import { t as assertNotEquals } from "../assert_not_equals-DkVK8oqV.mjs";
7
8
  import { t as MemoryKvStore } from "../kv-rV3vodCc.mjs";
8
- import { o as createFederation, s as handleWebFinger } from "../middleware-K1g6bEkV.mjs";
9
- import { test } from "@fedify/fixture";
9
+ import { o as createFederation, s as handleWebFinger } from "../middleware-Cg4EyWsq.mjs";
10
10
  import { Image, Link, Person, Tombstone } from "@fedify/vocab";
11
+ import { createTestMeterProvider, test } from "@fedify/fixture";
11
12
  //#region src/federation/webfinger.test.ts
12
13
  test("handleWebFinger()", async (t) => {
13
14
  const url = new URL("https://example.com/.well-known/webfinger");
@@ -532,5 +533,149 @@ test("handleWebFinger()", async (t) => {
532
533
  });
533
534
  });
534
535
  });
536
+ test("handleWebFinger() records webfinger.handle counter and duration", async (t) => {
537
+ const url = new URL("https://example.com/.well-known/webfinger");
538
+ const actorDispatcher = (ctx, identifier) => {
539
+ if (identifier === "gone") return new Tombstone({ id: ctx.getActorUri(identifier) });
540
+ if (identifier !== "someone") return null;
541
+ return new Person({
542
+ id: ctx.getActorUri(identifier),
543
+ preferredUsername: "someone"
544
+ });
545
+ };
546
+ const onNotFound = () => new Response("Not found", { status: 404 });
547
+ function createContext(u) {
548
+ return createRequestContext({
549
+ federation: createFederation({ kv: new MemoryKvStore() }),
550
+ url: u,
551
+ data: void 0,
552
+ getActorUri(identifier) {
553
+ return new URL(`${u.origin}/users/${identifier}`);
554
+ },
555
+ async getActor(handle) {
556
+ const actor = await actorDispatcher(this, handle);
557
+ return actor instanceof Tombstone ? null : actor;
558
+ },
559
+ parseUri(uri) {
560
+ if (uri == null) return null;
561
+ if (uri.protocol === "acct:") return null;
562
+ if (!uri.pathname.startsWith("/users/")) return null;
563
+ return {
564
+ type: "actor",
565
+ identifier: uri.pathname.split("/").pop()
566
+ };
567
+ }
568
+ });
569
+ }
570
+ await t.step("records result=resolved for a 200 response", async () => {
571
+ const u = new URL(url);
572
+ u.searchParams.set("resource", "acct:someone@example.com");
573
+ const context = createContext(u);
574
+ const [meterProvider, recorder] = createTestMeterProvider();
575
+ assertEquals((await handleWebFinger(context.request, {
576
+ context,
577
+ actorDispatcher,
578
+ onNotFound,
579
+ meterProvider
580
+ })).status, 200);
581
+ const counter = recorder.getMeasurement("webfinger.handle");
582
+ assertNotEquals(counter, void 0);
583
+ assertEquals(counter?.type, "counter");
584
+ assertEquals(counter?.value, 1);
585
+ assertEquals(counter?.attributes["webfinger.handle.result"], "resolved");
586
+ assertEquals(counter?.attributes["webfinger.resource.scheme"], "acct");
587
+ assertEquals(counter?.attributes["http.response.status_code"], 200);
588
+ const duration = recorder.getMeasurement("webfinger.handle.duration");
589
+ assertNotEquals(duration, void 0);
590
+ assertEquals(duration?.type, "histogram");
591
+ assertEquals(duration?.attributes["webfinger.handle.result"], "resolved");
592
+ });
593
+ await t.step("records result=invalid for a 400 response", async () => {
594
+ const context = createContext(new URL(url));
595
+ const [meterProvider, recorder] = createTestMeterProvider();
596
+ assertEquals((await handleWebFinger(context.request, {
597
+ context,
598
+ actorDispatcher,
599
+ onNotFound,
600
+ meterProvider
601
+ })).status, 400);
602
+ const counter = recorder.getMeasurement("webfinger.handle");
603
+ assertEquals(counter?.attributes["webfinger.handle.result"], "invalid");
604
+ assertEquals(counter?.attributes["http.response.status_code"], 400);
605
+ assertEquals("webfinger.resource.scheme" in (counter?.attributes ?? {}), false, "missing resource has no scheme attribute");
606
+ });
607
+ await t.step("records result=not_found for a 404 response", async () => {
608
+ const u = new URL(url);
609
+ u.searchParams.set("resource", "acct:absent@example.com");
610
+ const context = createContext(u);
611
+ const [meterProvider, recorder] = createTestMeterProvider();
612
+ assertEquals((await handleWebFinger(context.request, {
613
+ context,
614
+ actorDispatcher,
615
+ onNotFound,
616
+ meterProvider
617
+ })).status, 404);
618
+ const counter = recorder.getMeasurement("webfinger.handle");
619
+ assertEquals(counter?.attributes["webfinger.handle.result"], "not_found");
620
+ assertEquals(counter?.attributes["http.response.status_code"], 404);
621
+ assertEquals(counter?.attributes["webfinger.resource.scheme"], "acct");
622
+ });
623
+ await t.step("records result=tombstoned for a 410 response", async () => {
624
+ const u = new URL(url);
625
+ u.searchParams.set("resource", "acct:gone@example.com");
626
+ const context = createContext(u);
627
+ const [meterProvider, recorder] = createTestMeterProvider();
628
+ assertEquals((await handleWebFinger(context.request, {
629
+ context,
630
+ actorDispatcher,
631
+ onNotFound,
632
+ meterProvider
633
+ })).status, 410);
634
+ const counter = recorder.getMeasurement("webfinger.handle");
635
+ assertEquals(counter?.attributes["webfinger.handle.result"], "tombstoned");
636
+ assertEquals(counter?.attributes["http.response.status_code"], 410);
637
+ });
638
+ await t.step("records result=not_found when onNotFound returns 200", async () => {
639
+ const u = new URL(url);
640
+ u.searchParams.set("resource", "acct:absent@example.com");
641
+ const context = createContext(u);
642
+ const [meterProvider, recorder] = createTestMeterProvider();
643
+ assertEquals((await handleWebFinger(context.request, {
644
+ context,
645
+ actorDispatcher,
646
+ onNotFound: () => new Response("custom fallback", { status: 200 }),
647
+ meterProvider
648
+ })).status, 200);
649
+ const counter = recorder.getMeasurement("webfinger.handle");
650
+ assertEquals(counter?.attributes["webfinger.handle.result"], "not_found");
651
+ assertEquals(counter?.attributes["http.response.status_code"], 200);
652
+ });
653
+ await t.step("buckets unknown resource schemes as 'other' to keep metric cardinality bounded", async () => {
654
+ const u = new URL(url);
655
+ u.searchParams.set("resource", "ssh:nobody@example.com");
656
+ const context = createContext(u);
657
+ const [meterProvider, recorder] = createTestMeterProvider();
658
+ await handleWebFinger(context.request, {
659
+ context,
660
+ actorDispatcher,
661
+ onNotFound,
662
+ meterProvider
663
+ });
664
+ assertEquals(recorder.getMeasurement("webfinger.handle")?.attributes["webfinger.resource.scheme"], "other");
665
+ });
666
+ await t.step("omits measurements when no meterProvider is provided", async () => {
667
+ const u = new URL(url);
668
+ u.searchParams.set("resource", "acct:someone@example.com");
669
+ const context = createContext(u);
670
+ const [_unused, recorder] = createTestMeterProvider();
671
+ await handleWebFinger(context.request, {
672
+ context,
673
+ actorDispatcher,
674
+ onNotFound
675
+ });
676
+ assertEquals(recorder.getMeasurements("webfinger.handle").length, 0);
677
+ assertEquals(recorder.getMeasurements("webfinger.handle.duration").length, 0);
678
+ });
679
+ });
535
680
  //#endregion
536
681
  export {};