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

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-5M0ZV0s3.mjs} +2 -2
  2. package/dist/compat/transformers.test.mjs +1 -1
  3. package/dist/{deno-D9LpbVQR.mjs → deno-BvRV20GH.mjs} +1 -1
  4. package/dist/{docloader-y2viZ2Tx.mjs → docloader-YOsFpxMZ.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-D6yvDhyL.mjs → http-BETUCsB0.mjs} +2 -2
  14. package/dist/{http-CA4xKsSY.js → http-Cj-JmUpS.js} +1 -1
  15. package/dist/{http-Bj0uN6d-.cjs → http-DT4PVP4u.cjs} +1 -1
  16. package/dist/{key-BPjHWwyv.mjs → key-CjtOXvjb.mjs} +1 -1
  17. package/dist/{kv-cache-DarvCOHt.js → kv-cache-BOU_yvQi.js} +1 -1
  18. package/dist/{kv-cache-BdSTsjLb.cjs → kv-cache-DqiCNMDz.cjs} +1 -1
  19. package/dist/{ld-D9yQwfkO.mjs → ld-WRlX-hAe.mjs} +2 -2
  20. package/dist/{middleware-0n0ctSu_.js → middleware-B0YQlYeu.js} +96 -7
  21. package/dist/{middleware-THfK90u_.mjs → middleware-BD-WrN2n.mjs} +50 -13
  22. package/dist/{middleware-Ccpokmfe.cjs → middleware-C-fzauLy.cjs} +97 -8
  23. package/dist/{middleware-C5ao_lvm.mjs → middleware-CAUZmBAN.mjs} +1 -1
  24. package/dist/{middleware-DTxZNOqy.cjs → middleware-D3TvoZdj.cjs} +1 -1
  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-B6F-ovsj.mjs} +2 -2
  29. package/dist/{proof-UXZOysVc.mjs → proof-CN82dCfF.mjs} +2 -2
  30. package/dist/{proof-C2QsttUL.cjs → proof-CP3pE1Ok.cjs} +1 -1
  31. package/dist/{proof-Cdxbeq4n.js → proof-DCfIkmPR.js} +1 -1
  32. package/dist/{send-_8qtDYZA.mjs → send-DNs8Rwv0.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 +6 -6
@@ -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-BvRV20GH.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-CAUZmBAN.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-BD-WrN2n.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.1050+74157106";
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-CjtOXvjb.mjs";
5
+ import { n as doubleKnock } from "./http-BETUCsB0.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-5M0ZV0s3.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-BETUCsB0.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-BD-WrN2n.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-CN82dCfF.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-BD-WrN2n.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-BETUCsB0.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-YOsFpxMZ.mjs";
17
+ import { a as signJsonLd, o as verifyJsonLd, r as detachSignature } from "../ld-WRlX-hAe.mjs";
18
+ import { t as doesActorOwnKey } from "../owner-B6F-ovsj.mjs";
19
+ import { i as verifyObject, r as signObject } from "../proof-CN82dCfF.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-BD-WrN2n.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-C-fzauLy.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-B0YQlYeu.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-BETUCsB0.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-B6F-ovsj.mjs";
15
+ import { n as extractInboxes, r as sendActivity, t as SendActivityError } from "../send-DNs8Rwv0.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-BD-WrN2n.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
@@ -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-BvRV20GH.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-CjtOXvjb.mjs";
7
7
  import { CryptographicKey } from "@fedify/vocab";
8
8
  import { SpanStatusCode, trace } from "@opentelemetry/api";
9
9
  import { FetchError } from "@fedify/vocab-runtime";
@@ -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.1050+74157106";
14
14
  //#endregion
15
15
  //#region src/sig/accept.ts
16
16
  /**
@@ -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.1050+74157106";
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-BvRV20GH.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,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-Cj-JmUpS.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,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-DT4PVP4u.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,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-BvRV20GH.mjs";
5
+ import { n as fetchKey, o as validateCryptoKey } from "./key-CjtOXvjb.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";
@@ -2,10 +2,10 @@ import { Temporal } from "@js-temporal/polyfill";
2
2
  import "urlpattern-polyfill";
3
3
  import { t as __exportAll } from "./chunk-nlSIicah.js";
4
4
  import { r as getDefaultActivityTransformers } from "./transformers-ve6e2xcg.js";
5
- import { _ as version, a as verifyRequestDetailed, d as validateCryptoKey, f as formatAcceptSignature, g as name, i as verifyRequest, n as parseRfc9421SignatureInput, o as exportJwk, t as doubleKnock, u as importJwk } from "./http-CA4xKsSY.js";
6
- import { c as getKeyOwner, d as detachSignature, f as hasSignatureLike, i as verifyObject, m as verifyJsonLd, n as hasProofLike, o as normalizeOutgoingActivityJsonLd, p as signJsonLd, r as signObject, s as doesActorOwnKey } from "./proof-Cdxbeq4n.js";
5
+ import { _ as version, a as verifyRequestDetailed, d as validateCryptoKey, f as formatAcceptSignature, g as name, i as verifyRequest, n as parseRfc9421SignatureInput, o as exportJwk, t as doubleKnock, u as importJwk } from "./http-Cj-JmUpS.js";
6
+ import { c as getKeyOwner, d as detachSignature, f as hasSignatureLike, i as verifyObject, m as verifyJsonLd, n as hasProofLike, o as normalizeOutgoingActivityJsonLd, p as signJsonLd, r as signObject, s as doesActorOwnKey } from "./proof-DCfIkmPR.js";
7
7
  import { n as getNodeInfo, t as nodeInfoToJson } from "./types-hvL8ElAs.js";
8
- import { n as getAuthenticatedDocumentLoader, t as kvCache } from "./kv-cache-DarvCOHt.js";
8
+ import { n as getAuthenticatedDocumentLoader, t as kvCache } from "./kv-cache-BOU_yvQi.js";
9
9
  import { getLogger, withContext } from "@logtape/logtape";
10
10
  import { Activity, Collection, CollectionPage, CryptographicKey, Link, Multikey, Object as Object$1, OrderedCollection, OrderedCollectionPage, Tombstone, getTypeId, lookupObject, traverseCollection } from "@fedify/vocab";
11
11
  import { SpanKind, SpanStatusCode, context, metrics, propagation, trace } from "@opentelemetry/api";
@@ -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(name, 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.
@@ -3577,6 +3629,8 @@ var FederationImpl = class extends FederationBuilderImpl {
3577
3629
  fetch(request, options) {
3578
3630
  return withContext({ requestId: getRequestId(request) }, async () => {
3579
3631
  const tracer = this._getTracer();
3632
+ const metricState = {};
3633
+ const metricStart = performance.now();
3580
3634
  return await tracer.startActiveSpan(request.method, {
3581
3635
  kind: SpanKind.SERVER,
3582
3636
  attributes: {
@@ -3600,10 +3654,12 @@ var FederationImpl = class extends FederationBuilderImpl {
3600
3654
  response = await this.#fetch(request, {
3601
3655
  ...options,
3602
3656
  span,
3603
- tracer
3657
+ tracer,
3658
+ metricState
3604
3659
  });
3605
3660
  if (acceptsJsonLd(request)) response.headers.set("Vary", "Accept");
3606
3661
  } catch (error) {
3662
+ getFederationMetrics(this.meterProvider).recordHttpServerRequest(request.method, metricState.endpoint ?? "error", getDurationMs(metricStart), { routeTemplate: metricState.routeTemplate });
3607
3663
  span.setStatus({
3608
3664
  code: SpanStatusCode.ERROR,
3609
3665
  message: `${error}`
@@ -3616,6 +3672,10 @@ var FederationImpl = class extends FederationBuilderImpl {
3616
3672
  });
3617
3673
  throw error;
3618
3674
  }
3675
+ getFederationMetrics(this.meterProvider).recordHttpServerRequest(request.method, metricState.endpoint ?? "error", getDurationMs(metricStart), {
3676
+ statusCode: response.status,
3677
+ routeTemplate: metricState.routeTemplate
3678
+ });
3619
3679
  if (span.isRecording()) {
3620
3680
  span.setAttribute(ATTR_HTTP_RESPONSE_STATUS_CODE, response.status);
3621
3681
  for (const [k, v] of response.headers) span.setAttribute(ATTR_HTTP_RESPONSE_HEADER(k), [v]);
@@ -3641,13 +3701,18 @@ var FederationImpl = class extends FederationBuilderImpl {
3641
3701
  });
3642
3702
  });
3643
3703
  }
3644
- async #fetch(request, { onNotFound, onNotAcceptable, onUnauthorized, contextData, span, tracer }) {
3704
+ async #fetch(request, { onNotFound, onNotAcceptable, onUnauthorized, contextData, span, tracer, metricState }) {
3645
3705
  onNotFound ??= notFound;
3646
3706
  onNotAcceptable ??= notAcceptable;
3647
3707
  onUnauthorized ??= unauthorized;
3648
3708
  const url = new URL(request.url);
3649
3709
  const route = this.router.route(url.pathname);
3650
- if (route == null) return await onNotFound(request);
3710
+ if (route == null) {
3711
+ metricState.endpoint = "not_found";
3712
+ return await onNotFound(request);
3713
+ }
3714
+ metricState.routeTemplate = route.template;
3715
+ metricState.endpoint = getEndpointCategory(route.name);
3651
3716
  span.updateName(`${request.method} ${route.template}`);
3652
3717
  let context = this.#createContext(request, contextData);
3653
3718
  const routeName = route.name.replace(/:.*$/, "");
@@ -3668,7 +3733,10 @@ var FederationImpl = class extends FederationBuilderImpl {
3668
3733
  nodeInfoDispatcher: this.nodeInfoDispatcher
3669
3734
  });
3670
3735
  }
3671
- if (request.method !== "POST" && !acceptsJsonLd(request)) return await onNotAcceptable(request);
3736
+ if (request.method !== "POST" && !acceptsJsonLd(request)) {
3737
+ metricState.endpoint = "not_acceptable";
3738
+ return await onNotAcceptable(request);
3739
+ }
3672
3740
  switch (routeName) {
3673
3741
  case "actor":
3674
3742
  case "actorAlias": {
@@ -3859,12 +3927,33 @@ var FederationImpl = class extends FederationBuilderImpl {
3859
3927
  });
3860
3928
  }
3861
3929
  default: {
3930
+ metricState.endpoint = "not_found";
3862
3931
  const response = onNotFound(request);
3863
3932
  return response instanceof Promise ? await response : response;
3864
3933
  }
3865
3934
  }
3866
3935
  }
3867
3936
  };
3937
+ function getEndpointCategory(routeName) {
3938
+ if (routeName.startsWith("object:")) return "object";
3939
+ if (routeName.startsWith("collection:") || routeName.startsWith("orderedCollection:")) return "collection";
3940
+ if (routeName.startsWith("actorAlias:")) return "actor";
3941
+ switch (routeName) {
3942
+ case "webfinger": return "webfinger";
3943
+ case "nodeInfoJrd":
3944
+ case "nodeInfo": return "nodeinfo";
3945
+ case "actor": return "actor";
3946
+ case "inbox": return "inbox";
3947
+ case "sharedInbox": return "shared_inbox";
3948
+ case "outbox": return "outbox";
3949
+ case "following": return "following";
3950
+ case "followers": return "followers";
3951
+ case "liked": return "liked";
3952
+ case "featured": return "featured";
3953
+ case "featuredTags": return "featured_tags";
3954
+ default: return "not_found";
3955
+ }
3956
+ }
3868
3957
  const FANOUT_THRESHOLD = 5;
3869
3958
  var ContextImpl = class ContextImpl {
3870
3959
  url;