@fedify/fedify 2.3.0-dev.1048 → 2.3.0-dev.1069

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 (43) hide show
  1. package/dist/{builder-BhiIuyGK.mjs → builder-DQ2zYTeA.mjs} +2 -2
  2. package/dist/compat/transformers.test.mjs +1 -1
  3. package/dist/{deno-D9LpbVQR.mjs → deno-CFXqOz6w.mjs} +1 -1
  4. package/dist/{docloader-y2viZ2Tx.mjs → docloader-QNtAtTZF.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/middleware.test.mjs +199 -6
  9. package/dist/federation/mod.cjs +1 -1
  10. package/dist/federation/mod.js +1 -1
  11. package/dist/federation/send.test.mjs +3 -3
  12. package/dist/federation/webfinger.test.mjs +1 -1
  13. package/dist/{http-CA4xKsSY.js → http-D2EDlTr2.js} +1 -1
  14. package/dist/{http-D6yvDhyL.mjs → http-QHJGzUe8.mjs} +2 -2
  15. package/dist/{http-Bj0uN6d-.cjs → http-e1wtIlFo.cjs} +1 -1
  16. package/dist/{key-BPjHWwyv.mjs → key-CpAxygvh.mjs} +1 -1
  17. package/dist/{kv-cache-BdSTsjLb.cjs → kv-cache-B3GfB70S.cjs} +1 -1
  18. package/dist/{kv-cache-DarvCOHt.js → kv-cache-KLjvIlKt.js} +1 -1
  19. package/dist/{ld-D9yQwfkO.mjs → ld-Ce_vkKjG.mjs} +2 -2
  20. package/dist/{middleware-C5ao_lvm.mjs → middleware-BJMPv7_l.mjs} +1 -1
  21. package/dist/{middleware-DTxZNOqy.cjs → middleware-CibncbiT.cjs} +1 -1
  22. package/dist/{middleware-Ccpokmfe.cjs → middleware-DOLrvK_b.cjs} +97 -8
  23. package/dist/{middleware-0n0ctSu_.js → middleware-De241etq.js} +96 -7
  24. package/dist/{middleware-THfK90u_.mjs → middleware-DsGmXfXz.mjs} +50 -13
  25. package/dist/mod.cjs +4 -4
  26. package/dist/mod.js +4 -4
  27. package/dist/nodeinfo/handler.test.mjs +1 -1
  28. package/dist/{owner-CebIXUof.mjs → owner-DmgzyItA.mjs} +2 -2
  29. package/dist/{proof-C2QsttUL.cjs → proof-BU1TpFYI.cjs} +1 -1
  30. package/dist/{proof-UXZOysVc.mjs → proof-C3q2IhUr.mjs} +2 -2
  31. package/dist/{proof-Cdxbeq4n.js → proof-DLDsFYfD.js} +1 -1
  32. package/dist/{send-_8qtDYZA.mjs → send-CTi2iPXp.mjs} +54 -2
  33. package/dist/sig/http.test.mjs +2 -2
  34. package/dist/sig/key.test.mjs +1 -1
  35. package/dist/sig/ld.test.mjs +2 -2
  36. package/dist/sig/mod.cjs +2 -2
  37. package/dist/sig/mod.js +2 -2
  38. package/dist/sig/owner.test.mjs +1 -1
  39. package/dist/sig/proof.test.mjs +1 -1
  40. package/dist/utils/docloader.test.mjs +2 -2
  41. package/dist/utils/mod.cjs +1 -1
  42. package/dist/utils/mod.js +1 -1
  43. package/package.json +5 -5
@@ -2,7 +2,7 @@ import "@js-temporal/polyfill";
2
2
  import "urlpattern-polyfill";
3
3
  globalThis.addEventListener = () => {};
4
4
  import { n as RouterError, t as Router } from "./router-CrMLXoOr.mjs";
5
- import { n as version, t as name } from "./deno-D9LpbVQR.mjs";
5
+ import { n as version, t as name } from "./deno-CFXqOz6w.mjs";
6
6
  import { t as ActivityListenerSet } from "./activity-listener-tztVvlNb.mjs";
7
7
  import { Tombstone, getTypeId } from "@fedify/vocab";
8
8
  import { SpanKind, SpanStatusCode, trace } from "@opentelemetry/api";
@@ -59,7 +59,7 @@ var FederationBuilderImpl = class {
59
59
  this.collectionTypeIds = {};
60
60
  }
61
61
  async build(options) {
62
- const { FederationImpl } = await import("./middleware-C5ao_lvm.mjs");
62
+ const { FederationImpl } = await import("./middleware-BJMPv7_l.mjs");
63
63
  const f = new FederationImpl(options);
64
64
  const trailingSlashInsensitiveValue = f.router.trailingSlashInsensitive;
65
65
  f.router = this.router.clone();
@@ -5,7 +5,7 @@ import { t as assertEquals } from "../assert_equals-Ew3jOFa3.mjs";
5
5
  import { t as assertInstanceOf } from "../assert_instance_of-C4Ri6VuN.mjs";
6
6
  import { t as assert } from "../assert-DikXweDx.mjs";
7
7
  import { t as MemoryKvStore } from "../kv-x2IvBUyq.mjs";
8
- import { n as FederationImpl, v as actorDehydrator, y as autoIdAssigner } from "../middleware-THfK90u_.mjs";
8
+ import { n as FederationImpl, v as actorDehydrator, y as autoIdAssigner } from "../middleware-DsGmXfXz.mjs";
9
9
  import { test } from "@fedify/fixture";
10
10
  import { Follow, Person } from "@fedify/vocab";
11
11
  //#region src/compat/transformers.test.ts
@@ -3,6 +3,6 @@ import "urlpattern-polyfill";
3
3
  globalThis.addEventListener = () => {};
4
4
  //#region deno.json
5
5
  var name = "@fedify/fedify";
6
- var version = "2.3.0-dev.1048+2da74497";
6
+ var version = "2.3.0-dev.1069+81e910ce";
7
7
  //#endregion
8
8
  export { version as n, name as t };
@@ -1,8 +1,8 @@
1
1
  import "@js-temporal/polyfill";
2
2
  import "urlpattern-polyfill";
3
3
  globalThis.addEventListener = () => {};
4
- import { o as validateCryptoKey } from "./key-BPjHWwyv.mjs";
5
- import { n as doubleKnock } from "./http-D6yvDhyL.mjs";
4
+ import { o as validateCryptoKey } from "./key-CpAxygvh.mjs";
5
+ import { n as doubleKnock } from "./http-QHJGzUe8.mjs";
6
6
  import { curry } from "es-toolkit";
7
7
  import { UrlError, createActivityPubRequest, getRemoteDocument, logRequest, validatePublicUrl } from "@fedify/vocab-runtime";
8
8
  import { getLogger } from "@logtape/logtape";
@@ -6,7 +6,7 @@ import { t as assertEquals } from "../assert_equals-Ew3jOFa3.mjs";
6
6
  import { i as assertExists } from "../std__assert-CRDpx_HF.mjs";
7
7
  import { t as assertThrows } from "../assert_throws-4NwKEy2q.mjs";
8
8
  import { t as MemoryKvStore } from "../kv-x2IvBUyq.mjs";
9
- import { r as createFederationBuilder } from "../builder-BhiIuyGK.mjs";
9
+ import { r as createFederationBuilder } from "../builder-DQ2zYTeA.mjs";
10
10
  import { test } from "@fedify/fixture";
11
11
  import { Activity, Note, Person } from "@fedify/vocab";
12
12
  //#region src/federation/builder.test.ts
@@ -7,10 +7,10 @@ import { r as assertGreaterOrEqual } from "../std__assert-CRDpx_HF.mjs";
7
7
  import { t as assertInstanceOf } from "../assert_instance_of-C4Ri6VuN.mjs";
8
8
  import { t as assert } from "../assert-DikXweDx.mjs";
9
9
  import { r as parseAcceptSignature } from "../accept-CceiKpCy.mjs";
10
- import { s as signRequest } from "../http-D6yvDhyL.mjs";
10
+ import { s as signRequest } from "../http-QHJGzUe8.mjs";
11
11
  import { a as rsaPrivateKey3, c as rsaPublicKey3, s as rsaPublicKey2 } from "../keys-C3kae-6B.mjs";
12
12
  import { t as MemoryKvStore } from "../kv-x2IvBUyq.mjs";
13
- import { c as handleActor, d as handleInbox, f as handleObject, h as respondWithObjectIfAcceptable, l as handleCollection, m as respondWithObject, o as createFederation, p as handleOutbox, u as handleCustomCollection } from "../middleware-THfK90u_.mjs";
13
+ import { c as handleActor, d as handleInbox, f as handleObject, h as respondWithObjectIfAcceptable, l as handleCollection, m as respondWithObject, o as createFederation, p as handleOutbox, u as handleCustomCollection } from "../middleware-DsGmXfXz.mjs";
14
14
  import { t as ActivityListenerSet } from "../activity-listener-tztVvlNb.mjs";
15
15
  import { createTestMeterProvider, createTestTracerProvider, mockDocumentLoader, test } from "@fedify/fixture";
16
16
  import { Activity, Create, Note, Person, Tombstone } from "@fedify/vocab";
@@ -4,9 +4,9 @@ globalThis.addEventListener = () => {};
4
4
  import { t as assertEquals } from "../assert_equals-Ew3jOFa3.mjs";
5
5
  import "../std__assert-CRDpx_HF.mjs";
6
6
  import { n as ed25519PrivateKey, r as ed25519PublicKey, t as ed25519Multikey } from "../keys-C3kae-6B.mjs";
7
- import { r as signObject } from "../proof-UXZOysVc.mjs";
7
+ import { r as signObject } from "../proof-C3q2IhUr.mjs";
8
8
  import { t as MemoryKvStore } from "../kv-x2IvBUyq.mjs";
9
- import { o as createFederation } from "../middleware-THfK90u_.mjs";
9
+ import { o as createFederation } from "../middleware-DsGmXfXz.mjs";
10
10
  import { mockDocumentLoader, test } from "@fedify/fixture";
11
11
  import { Create, Follow, Person } from "@fedify/vocab";
12
12
  //#region src/federation/idempotency.test.ts
@@ -11,14 +11,14 @@ import { t as assertNotEquals } from "../assert_not_equals--wG9hV7u.mjs";
11
11
  import { t as assertStrictEquals } from "../assert_strict_equals-Dmjbg-bA.mjs";
12
12
  import { t as assert } from "../assert-DikXweDx.mjs";
13
13
  import { t as esm_default } from "../esm-DhnRLoG9.mjs";
14
- import { l as verifyRequest, s as signRequest } from "../http-D6yvDhyL.mjs";
14
+ import { l as verifyRequest, s as signRequest } from "../http-QHJGzUe8.mjs";
15
15
  import { a as rsaPrivateKey3, c as rsaPublicKey3, i as rsaPrivateKey2, n as ed25519PrivateKey, r as ed25519PublicKey, s as rsaPublicKey2, t as ed25519Multikey } from "../keys-C3kae-6B.mjs";
16
- import { t as getAuthenticatedDocumentLoader } from "../docloader-y2viZ2Tx.mjs";
17
- import { a as signJsonLd, o as verifyJsonLd, r as detachSignature } from "../ld-D9yQwfkO.mjs";
18
- import { t as doesActorOwnKey } from "../owner-CebIXUof.mjs";
19
- import { i as verifyObject, r as signObject } from "../proof-UXZOysVc.mjs";
16
+ import { t as getAuthenticatedDocumentLoader } from "../docloader-QNtAtTZF.mjs";
17
+ import { a as signJsonLd, o as verifyJsonLd, r as detachSignature } from "../ld-Ce_vkKjG.mjs";
18
+ import { t as doesActorOwnKey } from "../owner-DmgzyItA.mjs";
19
+ import { i as verifyObject, r as signObject } from "../proof-C3q2IhUr.mjs";
20
20
  import { t as MemoryKvStore } from "../kv-x2IvBUyq.mjs";
21
- import { i as KvSpecDeterminer, n as FederationImpl, o as createFederation, r as InboxContextImpl, t as ContextImpl } from "../middleware-THfK90u_.mjs";
21
+ import { i as KvSpecDeterminer, n as FederationImpl, o as createFederation, r as InboxContextImpl, t as ContextImpl } from "../middleware-DsGmXfXz.mjs";
22
22
  import { createTestMeterProvider, createTestTracerProvider, mockDocumentLoader, test } from "@fedify/fixture";
23
23
  import * as vocab from "@fedify/vocab";
24
24
  import { getTypeId, lookupObject } from "@fedify/vocab";
@@ -956,6 +956,199 @@ test("Federation.fetch()", async (t) => {
956
956
  });
957
957
  esm_default.hardReset();
958
958
  });
959
+ test("Federation.fetch() records HTTP server request metrics", async (t) => {
960
+ const createTestContext = () => {
961
+ const kv = new MemoryKvStore();
962
+ const [meterProvider, recorder] = createTestMeterProvider();
963
+ const federation = createFederation({
964
+ kv,
965
+ meterProvider,
966
+ documentLoaderFactory: () => mockDocumentLoader
967
+ });
968
+ federation.setActorDispatcher("/users/{identifier}", (ctx, identifier) => {
969
+ if (identifier === "boom") throw new Error("explosion in actor dispatcher");
970
+ return new vocab.Person({
971
+ id: ctx.getActorUri(identifier),
972
+ inbox: ctx.getInboxUri(identifier),
973
+ preferredUsername: identifier
974
+ });
975
+ });
976
+ federation.setNodeInfoDispatcher("/nodeinfo/2.1", () => ({
977
+ software: {
978
+ name: "example",
979
+ version: "1.0.0"
980
+ },
981
+ protocols: ["activitypub"],
982
+ usage: {
983
+ users: {},
984
+ localPosts: 0,
985
+ localComments: 0
986
+ }
987
+ }));
988
+ federation.setFollowersDispatcher("/users/{identifier}/followers", () => ({ items: [] }));
989
+ federation.setCollectionDispatcher("custom-collection", vocab.Object, "/users/{identifier}/custom/{id}", () => ({ items: [] }));
990
+ federation.setInboxListeners("/users/{identifier}/inbox", "/inbox");
991
+ return {
992
+ federation,
993
+ recorder
994
+ };
995
+ };
996
+ await t.step("records a successful actor request", async () => {
997
+ const { federation, recorder } = createTestContext();
998
+ assertEquals((await federation.fetch(new Request("https://example.com/users/alice", {
999
+ method: "GET",
1000
+ headers: { "Accept": "application/activity+json" }
1001
+ }), { contextData: void 0 })).status, 200);
1002
+ const counts = recorder.getMeasurements("fedify.http.server.request.count");
1003
+ assertEquals(counts.length, 1);
1004
+ assertEquals(counts[0].type, "counter");
1005
+ assertEquals(counts[0].value, 1);
1006
+ assertEquals(counts[0].attributes["http.request.method"], "GET");
1007
+ assertEquals(counts[0].attributes["fedify.endpoint"], "actor");
1008
+ assertEquals(counts[0].attributes["http.response.status_code"], 200);
1009
+ assertEquals(counts[0].attributes["fedify.route.template"], "/users/{identifier}");
1010
+ const durations = recorder.getMeasurements("fedify.http.server.request.duration");
1011
+ assertEquals(durations.length, 1);
1012
+ assertEquals(durations[0].type, "histogram");
1013
+ assert(durations[0].value >= 0);
1014
+ assertEquals(durations[0].attributes["fedify.endpoint"], "actor");
1015
+ assertEquals(durations[0].attributes["http.response.status_code"], 200);
1016
+ assertEquals(durations[0].attributes["fedify.route.template"], "/users/{identifier}");
1017
+ });
1018
+ await t.step("records WebFinger requests", async () => {
1019
+ const { federation, recorder } = createTestContext();
1020
+ assertEquals((await federation.fetch(new Request("https://example.com/.well-known/webfinger?resource=acct:alice@example.com"), { contextData: void 0 })).status, 200);
1021
+ const counts = recorder.getMeasurements("fedify.http.server.request.count");
1022
+ assertEquals(counts.length, 1);
1023
+ assertEquals(counts[0].attributes["fedify.endpoint"], "webfinger");
1024
+ assertEquals(counts[0].attributes["fedify.route.template"], "/.well-known/webfinger");
1025
+ assertEquals(counts[0].attributes["http.response.status_code"], 200);
1026
+ });
1027
+ await t.step("records NodeInfo JRD requests", async () => {
1028
+ const { federation, recorder } = createTestContext();
1029
+ assertEquals((await federation.fetch(new Request("https://example.com/.well-known/nodeinfo"), { contextData: void 0 })).status, 200);
1030
+ const counts = recorder.getMeasurements("fedify.http.server.request.count");
1031
+ assertEquals(counts.length, 1);
1032
+ assertEquals(counts[0].attributes["fedify.endpoint"], "nodeinfo");
1033
+ assertEquals(counts[0].attributes["fedify.route.template"], "/.well-known/nodeinfo");
1034
+ });
1035
+ await t.step("records NodeInfo dispatcher requests", async () => {
1036
+ const { federation, recorder } = createTestContext();
1037
+ assertEquals((await federation.fetch(new Request("https://example.com/nodeinfo/2.1"), { contextData: void 0 })).status, 200);
1038
+ const counts = recorder.getMeasurements("fedify.http.server.request.count");
1039
+ assertEquals(counts.length, 1);
1040
+ assertEquals(counts[0].attributes["fedify.endpoint"], "nodeinfo");
1041
+ assertEquals(counts[0].attributes["fedify.route.template"], "/nodeinfo/2.1");
1042
+ });
1043
+ await t.step("records 404 not_found for unmatched paths", async () => {
1044
+ const { federation, recorder } = createTestContext();
1045
+ assertEquals((await federation.fetch(new Request("https://example.com/no/such/path"), { contextData: void 0 })).status, 404);
1046
+ const counts = recorder.getMeasurements("fedify.http.server.request.count");
1047
+ assertEquals(counts.length, 1);
1048
+ assertEquals(counts[0].attributes["fedify.endpoint"], "not_found");
1049
+ assertEquals(counts[0].attributes["http.response.status_code"], 404);
1050
+ assertEquals(counts[0].attributes["fedify.route.template"], void 0);
1051
+ });
1052
+ await t.step("records 406 not_acceptable when JSON-LD Accept missing", async () => {
1053
+ const { federation, recorder } = createTestContext();
1054
+ assertEquals((await federation.fetch(new Request("https://example.com/users/alice", {
1055
+ method: "GET",
1056
+ headers: { "Accept": "text/html" }
1057
+ }), { contextData: void 0 })).status, 406);
1058
+ const counts = recorder.getMeasurements("fedify.http.server.request.count");
1059
+ assertEquals(counts.length, 1);
1060
+ assertEquals(counts[0].attributes["fedify.endpoint"], "not_acceptable");
1061
+ assertEquals(counts[0].attributes["http.response.status_code"], 406);
1062
+ assertEquals(counts[0].attributes["fedify.route.template"], "/users/{identifier}");
1063
+ });
1064
+ await t.step("records thrown errors after classification with the matched endpoint", async () => {
1065
+ const { federation, recorder } = createTestContext();
1066
+ await assertRejects(() => federation.fetch(new Request("https://example.com/users/boom", {
1067
+ method: "GET",
1068
+ headers: { "Accept": "application/activity+json" }
1069
+ }), { contextData: void 0 }), Error, "explosion");
1070
+ const counts = recorder.getMeasurements("fedify.http.server.request.count");
1071
+ assertEquals(counts.length, 1);
1072
+ assertEquals(counts[0].attributes["fedify.endpoint"], "actor");
1073
+ assertEquals(counts[0].attributes["http.response.status_code"], void 0);
1074
+ assertEquals(counts[0].attributes["fedify.route.template"], "/users/{identifier}");
1075
+ const durations = recorder.getMeasurements("fedify.http.server.request.duration");
1076
+ assertEquals(durations.length, 1);
1077
+ assertEquals(durations[0].attributes["fedify.endpoint"], "actor");
1078
+ });
1079
+ await t.step("collapses user-defined collection dispatchers to endpoint=collection", async () => {
1080
+ const { federation, recorder } = createTestContext();
1081
+ assertEquals((await federation.fetch(new Request("https://example.com/users/alice/custom/1", {
1082
+ method: "GET",
1083
+ headers: { "Accept": "application/activity+json" }
1084
+ }), { contextData: void 0 })).status, 200);
1085
+ const counts = recorder.getMeasurements("fedify.http.server.request.count");
1086
+ assertEquals(counts.length, 1);
1087
+ assertEquals(counts[0].attributes["fedify.endpoint"], "collection");
1088
+ assertEquals(counts[0].attributes["fedify.route.template"], "/users/{identifier}/custom/{id}");
1089
+ });
1090
+ await t.step("records followers as endpoint=followers", async () => {
1091
+ const { federation, recorder } = createTestContext();
1092
+ assertEquals((await federation.fetch(new Request("https://example.com/users/alice/followers", {
1093
+ method: "GET",
1094
+ headers: { "Accept": "application/activity+json" }
1095
+ }), { contextData: void 0 })).status, 200);
1096
+ const counts = recorder.getMeasurements("fedify.http.server.request.count");
1097
+ assertEquals(counts.length, 1);
1098
+ assertEquals(counts[0].attributes["fedify.endpoint"], "followers");
1099
+ assertEquals(counts[0].attributes["fedify.route.template"], "/users/{identifier}/followers");
1100
+ });
1101
+ await t.step("records sharedInbox as endpoint=shared_inbox", async () => {
1102
+ const kv = new MemoryKvStore();
1103
+ const [meterProvider, recorder] = createTestMeterProvider();
1104
+ const federation = createFederation({
1105
+ kv,
1106
+ meterProvider,
1107
+ documentLoaderFactory: () => mockDocumentLoader
1108
+ });
1109
+ federation.setInboxListeners("/users/{identifier}/inbox", "/inbox");
1110
+ assert((await federation.fetch(new Request("https://example.com/inbox", {
1111
+ method: "POST",
1112
+ headers: { "accept": "application/ld+json" }
1113
+ }), { contextData: void 0 })).status >= 400);
1114
+ const counts = recorder.getMeasurements("fedify.http.server.request.count");
1115
+ assertEquals(counts.length, 1);
1116
+ assertEquals(counts[0].attributes["fedify.endpoint"], "shared_inbox");
1117
+ assertEquals(counts[0].attributes["fedify.route.template"], "/inbox");
1118
+ assertEquals(counts[0].attributes["http.request.method"], "POST");
1119
+ });
1120
+ await t.step("normalizes unknown HTTP methods to _OTHER for cardinality control", async () => {
1121
+ const { federation, recorder } = createTestContext();
1122
+ assert((await federation.fetch(new Request("https://example.com/users/alice", {
1123
+ method: "PROPFIND",
1124
+ headers: { "Accept": "application/activity+json" }
1125
+ }), { contextData: void 0 })).status >= 100);
1126
+ const counts = recorder.getMeasurements("fedify.http.server.request.count");
1127
+ assertEquals(counts.length, 1);
1128
+ assertEquals(counts[0].attributes["http.request.method"], "_OTHER");
1129
+ });
1130
+ await t.step("preserves QUERY as a known HTTP method", async () => {
1131
+ const { federation, recorder } = createTestContext();
1132
+ assert((await federation.fetch(new Request("https://example.com/users/alice", {
1133
+ method: "QUERY",
1134
+ headers: { "Accept": "application/activity+json" }
1135
+ }), { contextData: void 0 })).status >= 100);
1136
+ const counts = recorder.getMeasurements("fedify.http.server.request.count");
1137
+ assertEquals(counts.length, 1);
1138
+ assertEquals(counts[0].attributes["http.request.method"], "QUERY");
1139
+ });
1140
+ await t.step("uses the global meter provider when none is configured", async () => {
1141
+ const federation = createFederation({
1142
+ kv: new MemoryKvStore(),
1143
+ documentLoaderFactory: () => mockDocumentLoader
1144
+ });
1145
+ federation.setActorDispatcher("/users/{identifier}", (ctx, identifier) => new vocab.Person({ id: ctx.getActorUri(identifier) }));
1146
+ assertEquals((await federation.fetch(new Request("https://example.com/users/alice", {
1147
+ method: "GET",
1148
+ headers: { "Accept": "application/activity+json" }
1149
+ }), { contextData: void 0 })).status, 200);
1150
+ });
1151
+ });
959
1152
  test("Federation.setInboxListeners()", async (t) => {
960
1153
  const kv = new MemoryKvStore();
961
1154
  esm_default.spyGlobal();
@@ -2,7 +2,7 @@ const { Temporal } = require("@js-temporal/polyfill");
2
2
  const { URLPattern } = require("urlpattern-polyfill");
3
3
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
4
4
  require("../chunk-DDcVe30Y.cjs");
5
- const require_middleware = require("../middleware-Ccpokmfe.cjs");
5
+ const require_middleware = require("../middleware-DOLrvK_b.cjs");
6
6
  let es_toolkit = require("es-toolkit");
7
7
  //#region src/federation/kv.ts
8
8
  /**
@@ -1,6 +1,6 @@
1
1
  import { Temporal } from "@js-temporal/polyfill";
2
2
  import "urlpattern-polyfill";
3
- import { a as createExponentialBackoffPolicy, c as buildCollectionSynchronizationHeader, d as Router, f as RouterError, i as SendActivityError, l as digest, o as respondWithObject, r as handleWebFinger, s as respondWithObjectIfAcceptable, t as createFederation, u as createFederationBuilder } from "../middleware-0n0ctSu_.js";
3
+ import { a as createExponentialBackoffPolicy, c as buildCollectionSynchronizationHeader, d as Router, f as RouterError, i as SendActivityError, l as digest, o as respondWithObject, r as handleWebFinger, s as respondWithObjectIfAcceptable, t as createFederation, u as createFederationBuilder } from "../middleware-De241etq.js";
4
4
  import { isEqual } from "es-toolkit";
5
5
  //#region src/federation/kv.ts
6
6
  /**
@@ -9,10 +9,10 @@ import { t as assertInstanceOf } from "../assert_instance_of-C4Ri6VuN.mjs";
9
9
  import { t as assertNotEquals } from "../assert_not_equals--wG9hV7u.mjs";
10
10
  import { t as assert } from "../assert-DikXweDx.mjs";
11
11
  import { t as esm_default } from "../esm-DhnRLoG9.mjs";
12
- import { l as verifyRequest } from "../http-D6yvDhyL.mjs";
12
+ import { l as verifyRequest } from "../http-QHJGzUe8.mjs";
13
13
  import { i as rsaPrivateKey2, n as ed25519PrivateKey, s as rsaPublicKey2, t as ed25519Multikey } from "../keys-C3kae-6B.mjs";
14
- import { t as doesActorOwnKey } from "../owner-CebIXUof.mjs";
15
- import { n as extractInboxes, r as sendActivity, t as SendActivityError } from "../send-_8qtDYZA.mjs";
14
+ import { t as doesActorOwnKey } from "../owner-DmgzyItA.mjs";
15
+ import { n as extractInboxes, r as sendActivity, t as SendActivityError } from "../send-CTi2iPXp.mjs";
16
16
  import { createTestMeterProvider, createTestTracerProvider, mockDocumentLoader, test } from "@fedify/fixture";
17
17
  import { Activity, Application, Endpoints, Group, Person, Service } from "@fedify/vocab";
18
18
  //#region ../../node_modules/.pnpm/@opentelemetry+sdk-metrics@2.5.0_@opentelemetry+api@1.9.0/node_modules/@opentelemetry/sdk-metrics/build/src/export/AggregationTemporality.js
@@ -5,7 +5,7 @@ import { r as createRequestContext } from "../context-7Azky82W.mjs";
5
5
  import { t as assertEquals } from "../assert_equals-Ew3jOFa3.mjs";
6
6
  import "../std__assert-CRDpx_HF.mjs";
7
7
  import { t as MemoryKvStore } from "../kv-x2IvBUyq.mjs";
8
- import { o as createFederation, s as handleWebFinger } from "../middleware-THfK90u_.mjs";
8
+ import { o as createFederation, s as handleWebFinger } from "../middleware-DsGmXfXz.mjs";
9
9
  import { test } from "@fedify/fixture";
10
10
  import { Image, Link, Person, Tombstone } from "@fedify/vocab";
11
11
  //#region src/federation/webfinger.test.ts
@@ -10,7 +10,7 @@ import { ATTR_HTTP_REQUEST_HEADER, ATTR_HTTP_REQUEST_METHOD, ATTR_URL_FULL } fro
10
10
  import { decodeBase64, encodeBase64 } from "byte-encodings/base64";
11
11
  //#region deno.json
12
12
  var name = "@fedify/fedify";
13
- var version = "2.3.0-dev.1048+2da74497";
13
+ var version = "2.3.0-dev.1069+81e910ce";
14
14
  //#endregion
15
15
  //#region src/sig/accept.ts
16
16
  /**
@@ -1,9 +1,9 @@
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-D9LpbVQR.mjs";
4
+ import { n as version, t as name } from "./deno-CFXqOz6w.mjs";
5
5
  import { i as validateAcceptSignature, n as fulfillAcceptSignature, r as parseAcceptSignature } from "./accept-CceiKpCy.mjs";
6
- import { o as validateCryptoKey, r as fetchKeyDetailed } from "./key-BPjHWwyv.mjs";
6
+ import { o as validateCryptoKey, r as fetchKeyDetailed } from "./key-CpAxygvh.mjs";
7
7
  import { CryptographicKey } from "@fedify/vocab";
8
8
  import { SpanStatusCode, trace } from "@opentelemetry/api";
9
9
  import { FetchError } from "@fedify/vocab-runtime";
@@ -11,7 +11,7 @@ let _opentelemetry_semantic_conventions = require("@opentelemetry/semantic-conve
11
11
  let byte_encodings_base64 = require("byte-encodings/base64");
12
12
  //#region deno.json
13
13
  var name = "@fedify/fedify";
14
- var version = "2.3.0-dev.1048+2da74497";
14
+ var version = "2.3.0-dev.1069+81e910ce";
15
15
  //#endregion
16
16
  //#region src/sig/accept.ts
17
17
  /**
@@ -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-D9LpbVQR.mjs";
4
+ import { n as version, t as name } from "./deno-CFXqOz6w.mjs";
5
5
  import { CryptographicKey, Object as Object$1, isActor } from "@fedify/vocab";
6
6
  import { SpanKind, SpanStatusCode, trace } from "@opentelemetry/api";
7
7
  import { FetchError, getDocumentLoader } from "@fedify/vocab-runtime";
@@ -1,7 +1,7 @@
1
1
  const { Temporal } = require("@js-temporal/polyfill");
2
2
  const { URLPattern } = require("urlpattern-polyfill");
3
3
  require("./chunk-DDcVe30Y.cjs");
4
- const require_http = require("./http-Bj0uN6d-.cjs");
4
+ const require_http = require("./http-e1wtIlFo.cjs");
5
5
  let _logtape_logtape = require("@logtape/logtape");
6
6
  let es_toolkit = require("es-toolkit");
7
7
  let _fedify_vocab_runtime = require("@fedify/vocab-runtime");
@@ -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 } from "./http-CA4xKsSY.js";
3
+ import { d as validateCryptoKey, t as doubleKnock } from "./http-D2EDlTr2.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,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-D9LpbVQR.mjs";
5
- import { n as fetchKey, o as validateCryptoKey } from "./key-BPjHWwyv.mjs";
4
+ import { n as version, t as name } from "./deno-CFXqOz6w.mjs";
5
+ import { n as fetchKey, o as validateCryptoKey } from "./key-CpAxygvh.mjs";
6
6
  import { Activity, CryptographicKey, Object as Object$1, getTypeId } from "@fedify/vocab";
7
7
  import { SpanStatusCode, trace } from "@opentelemetry/api";
8
8
  import { getDocumentLoader } from "@fedify/vocab-runtime";
@@ -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-THfK90u_.mjs";
4
+ import { n as FederationImpl } from "./middleware-DsGmXfXz.mjs";
5
5
  export { FederationImpl };
@@ -1,4 +1,4 @@
1
1
  const { Temporal } = require("@js-temporal/polyfill");
2
2
  const { URLPattern } = require("urlpattern-polyfill");
3
- const require_middleware = require("./middleware-Ccpokmfe.cjs");
3
+ const require_middleware = require("./middleware-DOLrvK_b.cjs");
4
4
  exports.FederationImpl = require_middleware.FederationImpl;
@@ -2,10 +2,10 @@ const { Temporal } = require("@js-temporal/polyfill");
2
2
  const { URLPattern } = require("urlpattern-polyfill");
3
3
  require("./chunk-DDcVe30Y.cjs");
4
4
  const require_transformers = require("./transformers-NeAONrAq.cjs");
5
- const require_http = require("./http-Bj0uN6d-.cjs");
6
- const require_proof = require("./proof-C2QsttUL.cjs");
5
+ const require_http = require("./http-e1wtIlFo.cjs");
6
+ const require_proof = require("./proof-BU1TpFYI.cjs");
7
7
  const require_types = require("./types-KC4QAoxe.cjs");
8
- const require_kv_cache = require("./kv-cache-BdSTsjLb.cjs");
8
+ const require_kv_cache = require("./kv-cache-B3GfB70S.cjs");
9
9
  let _logtape_logtape = require("@logtape/logtape");
10
10
  let _fedify_vocab = require("@fedify/vocab");
11
11
  let _opentelemetry_api = require("@opentelemetry/api");
@@ -211,7 +211,7 @@ var FederationBuilderImpl = class {
211
211
  this.collectionTypeIds = {};
212
212
  }
213
213
  async build(options) {
214
- const { FederationImpl } = await Promise.resolve().then(() => require("./middleware-DTxZNOqy.cjs"));
214
+ const { FederationImpl } = await Promise.resolve().then(() => require("./middleware-CibncbiT.cjs"));
215
215
  const f = new FederationImpl(options);
216
216
  const trailingSlashInsensitiveValue = f.router.trailingSlashInsensitive;
217
217
  f.router = this.router.clone();
@@ -786,6 +786,8 @@ var FederationMetrics = class {
786
786
  signatureVerificationFailure;
787
787
  deliveryDuration;
788
788
  inboxProcessingDuration;
789
+ httpServerRequestCount;
790
+ httpServerRequestDuration;
789
791
  constructor(meterProvider) {
790
792
  const meter = meterProvider.getMeter(require_http.name, require_http.version);
791
793
  this.deliverySent = meter.createCounter("activitypub.delivery.sent", {
@@ -808,6 +810,30 @@ var FederationMetrics = class {
808
810
  description: "Duration of ActivityPub inbox listener processing.",
809
811
  unit: "ms"
810
812
  });
813
+ this.httpServerRequestCount = meter.createCounter("fedify.http.server.request.count", {
814
+ description: "HTTP requests handled by Federation.fetch().",
815
+ unit: "{request}"
816
+ });
817
+ this.httpServerRequestDuration = meter.createHistogram("fedify.http.server.request.duration", {
818
+ description: "Duration of HTTP requests handled by Federation.fetch().",
819
+ unit: "ms",
820
+ advice: { explicitBucketBoundaries: [
821
+ 5,
822
+ 10,
823
+ 25,
824
+ 50,
825
+ 75,
826
+ 100,
827
+ 250,
828
+ 500,
829
+ 750,
830
+ 1e3,
831
+ 2500,
832
+ 5e3,
833
+ 7500,
834
+ 1e4
835
+ ] }
836
+ });
811
837
  }
812
838
  recordDelivery(inbox, durationMs, success, activityType) {
813
839
  const deliveryAttributes = {
@@ -832,7 +858,33 @@ var FederationMetrics = class {
832
858
  recordInboxProcessingDuration(activityType, durationMs) {
833
859
  this.inboxProcessingDuration.record(durationMs, { "activitypub.activity.type": activityType });
834
860
  }
861
+ recordHttpServerRequest(method, endpoint, durationMs, options = {}) {
862
+ const attributes = {
863
+ "http.request.method": normalizeHttpMethod(method),
864
+ "fedify.endpoint": endpoint
865
+ };
866
+ if (options.statusCode != null) attributes["http.response.status_code"] = options.statusCode;
867
+ if (options.routeTemplate != null) attributes["fedify.route.template"] = options.routeTemplate;
868
+ this.httpServerRequestCount.add(1, attributes);
869
+ this.httpServerRequestDuration.record(durationMs, attributes);
870
+ }
835
871
  };
872
+ const KNOWN_HTTP_METHODS = new Set([
873
+ "CONNECT",
874
+ "DELETE",
875
+ "GET",
876
+ "HEAD",
877
+ "OPTIONS",
878
+ "PATCH",
879
+ "POST",
880
+ "PUT",
881
+ "QUERY",
882
+ "TRACE"
883
+ ]);
884
+ function normalizeHttpMethod(method) {
885
+ const upper = method.toUpperCase();
886
+ return KNOWN_HTTP_METHODS.has(upper) ? upper : "_OTHER";
887
+ }
836
888
  const federationMetrics = /* @__PURE__ */ new WeakMap();
837
889
  /**
838
890
  * Gets the cached Fedify metric instruments for a meter provider.
@@ -3569,6 +3621,8 @@ var FederationImpl = class extends FederationBuilderImpl {
3569
3621
  fetch(request, options) {
3570
3622
  return (0, _logtape_logtape.withContext)({ requestId: getRequestId(request) }, async () => {
3571
3623
  const tracer = this._getTracer();
3624
+ const metricState = {};
3625
+ const metricStart = performance.now();
3572
3626
  return await tracer.startActiveSpan(request.method, {
3573
3627
  kind: _opentelemetry_api.SpanKind.SERVER,
3574
3628
  attributes: {
@@ -3592,10 +3646,12 @@ var FederationImpl = class extends FederationBuilderImpl {
3592
3646
  response = await this.#fetch(request, {
3593
3647
  ...options,
3594
3648
  span,
3595
- tracer
3649
+ tracer,
3650
+ metricState
3596
3651
  });
3597
3652
  if (acceptsJsonLd(request)) response.headers.set("Vary", "Accept");
3598
3653
  } catch (error) {
3654
+ getFederationMetrics(this.meterProvider).recordHttpServerRequest(request.method, metricState.endpoint ?? "error", getDurationMs(metricStart), { routeTemplate: metricState.routeTemplate });
3599
3655
  span.setStatus({
3600
3656
  code: _opentelemetry_api.SpanStatusCode.ERROR,
3601
3657
  message: `${error}`
@@ -3608,6 +3664,10 @@ var FederationImpl = class extends FederationBuilderImpl {
3608
3664
  });
3609
3665
  throw error;
3610
3666
  }
3667
+ getFederationMetrics(this.meterProvider).recordHttpServerRequest(request.method, metricState.endpoint ?? "error", getDurationMs(metricStart), {
3668
+ statusCode: response.status,
3669
+ routeTemplate: metricState.routeTemplate
3670
+ });
3611
3671
  if (span.isRecording()) {
3612
3672
  span.setAttribute(_opentelemetry_semantic_conventions.ATTR_HTTP_RESPONSE_STATUS_CODE, response.status);
3613
3673
  for (const [k, v] of response.headers) span.setAttribute((0, _opentelemetry_semantic_conventions.ATTR_HTTP_RESPONSE_HEADER)(k), [v]);
@@ -3633,13 +3693,18 @@ var FederationImpl = class extends FederationBuilderImpl {
3633
3693
  });
3634
3694
  });
3635
3695
  }
3636
- async #fetch(request, { onNotFound, onNotAcceptable, onUnauthorized, contextData, span, tracer }) {
3696
+ async #fetch(request, { onNotFound, onNotAcceptable, onUnauthorized, contextData, span, tracer, metricState }) {
3637
3697
  onNotFound ??= notFound;
3638
3698
  onNotAcceptable ??= notAcceptable;
3639
3699
  onUnauthorized ??= unauthorized;
3640
3700
  const url = new URL(request.url);
3641
3701
  const route = this.router.route(url.pathname);
3642
- if (route == null) return await onNotFound(request);
3702
+ if (route == null) {
3703
+ metricState.endpoint = "not_found";
3704
+ return await onNotFound(request);
3705
+ }
3706
+ metricState.routeTemplate = route.template;
3707
+ metricState.endpoint = getEndpointCategory(route.name);
3643
3708
  span.updateName(`${request.method} ${route.template}`);
3644
3709
  let context = this.#createContext(request, contextData);
3645
3710
  const routeName = route.name.replace(/:.*$/, "");
@@ -3660,7 +3725,10 @@ var FederationImpl = class extends FederationBuilderImpl {
3660
3725
  nodeInfoDispatcher: this.nodeInfoDispatcher
3661
3726
  });
3662
3727
  }
3663
- if (request.method !== "POST" && !acceptsJsonLd(request)) return await onNotAcceptable(request);
3728
+ if (request.method !== "POST" && !acceptsJsonLd(request)) {
3729
+ metricState.endpoint = "not_acceptable";
3730
+ return await onNotAcceptable(request);
3731
+ }
3664
3732
  switch (routeName) {
3665
3733
  case "actor":
3666
3734
  case "actorAlias": {
@@ -3851,12 +3919,33 @@ var FederationImpl = class extends FederationBuilderImpl {
3851
3919
  });
3852
3920
  }
3853
3921
  default: {
3922
+ metricState.endpoint = "not_found";
3854
3923
  const response = onNotFound(request);
3855
3924
  return response instanceof Promise ? await response : response;
3856
3925
  }
3857
3926
  }
3858
3927
  }
3859
3928
  };
3929
+ function getEndpointCategory(routeName) {
3930
+ if (routeName.startsWith("object:")) return "object";
3931
+ if (routeName.startsWith("collection:") || routeName.startsWith("orderedCollection:")) return "collection";
3932
+ if (routeName.startsWith("actorAlias:")) return "actor";
3933
+ switch (routeName) {
3934
+ case "webfinger": return "webfinger";
3935
+ case "nodeInfoJrd":
3936
+ case "nodeInfo": return "nodeinfo";
3937
+ case "actor": return "actor";
3938
+ case "inbox": return "inbox";
3939
+ case "sharedInbox": return "shared_inbox";
3940
+ case "outbox": return "outbox";
3941
+ case "following": return "following";
3942
+ case "followers": return "followers";
3943
+ case "liked": return "liked";
3944
+ case "featured": return "featured";
3945
+ case "featuredTags": return "featured_tags";
3946
+ default: return "not_found";
3947
+ }
3948
+ }
3860
3949
  const FANOUT_THRESHOLD = 5;
3861
3950
  var ContextImpl = class ContextImpl {
3862
3951
  url;