@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.
- package/README.md +16 -7
- package/dist/{assert-DikXweDx.mjs → assert-OguE97r2.mjs} +1 -1
- package/dist/{assert_instance_of-C4Ri6VuN.mjs → assert_instance_of-DBC5X09g.mjs} +1 -1
- package/dist/{assert_not_equals--wG9hV7u.mjs → assert_not_equals-DkVK8oqV.mjs} +1 -1
- package/dist/{assert_rejects-B-qJtC9Z.mjs → assert_rejects-DN60FHPX.mjs} +28 -3
- package/dist/{assert_strict_equals-Dmjbg-bA.mjs → assert_strict_equals-XEgZAlrj.mjs} +1 -1
- package/dist/{assert_throws-4NwKEy2q.mjs → assert_throws-BOkhLGYc.mjs} +1 -1
- package/dist/{builder-yAlpiIqP.mjs → builder-Dl5Sdmc9.mjs} +96 -42
- package/dist/{chunk-nlSIicah.js → chunk-CRNNMoPX.js} +2 -2
- package/dist/circuit-breaker-CSWsyoef.mjs +337 -0
- package/dist/{client-z-8dc-e1.d.cts → client-CAM_bQXx.d.cts} +1 -0
- package/dist/{client-AtlibPOU.d.ts → client-CSddvgWN.d.ts} +1 -2
- package/dist/compat/mod.d.cts +2 -1
- package/dist/compat/mod.d.ts +2 -3
- package/dist/compat/mod.js +3 -3
- package/dist/compat/outgoing-jsonld.test.mjs +4 -4
- package/dist/compat/public-audience.test.mjs +4 -4
- package/dist/compat/transformers.test.mjs +5 -5
- package/dist/{context-BzH2-ajs.d.ts → context-BBVLF7lx.d.cts} +342 -269
- package/dist/{context-DJGagtNd.d.cts → context-BU6jSQdo.d.ts} +344 -266
- package/dist/{context-Dk_tacqz.mjs → context-DVoTs_wM.mjs} +64 -5
- package/dist/{deno-XBVlKnOX.mjs → deno-BID9qTPk.mjs} +1 -1
- package/dist/{docloader-DPp-X6gk.mjs → docloader-BYFuVwwi.mjs} +4 -4
- package/dist/{esm-DVILvP5e.mjs → esm-vrlUxr60.mjs} +4 -7
- package/dist/federation/builder.test.mjs +163 -11
- package/dist/federation/circuit-breaker.test.d.mts +2 -0
- package/dist/federation/circuit-breaker.test.mjs +446 -0
- package/dist/federation/collection.test.mjs +3 -3
- package/dist/federation/handler.test.mjs +1770 -29
- package/dist/federation/idempotency.test.mjs +5 -5
- package/dist/federation/inbox.test.mjs +4 -4
- package/dist/federation/keycache.test.mjs +5 -5
- package/dist/federation/kv.test.mjs +2 -2
- package/dist/federation/metrics.test.d.mts +2 -0
- package/dist/federation/metrics.test.mjs +634 -0
- package/dist/federation/middleware.test.mjs +4036 -146
- package/dist/federation/mod.cjs +195 -14
- package/dist/federation/mod.d.cts +6 -4
- package/dist/federation/mod.d.ts +6 -6
- package/dist/federation/mod.js +192 -14
- package/dist/federation/mq.test.mjs +176 -15
- package/dist/federation/negotiation.test.mjs +4 -4
- package/dist/federation/retry.test.mjs +3 -3
- package/dist/federation/router.test.mjs +190 -9
- package/dist/federation/send.test.mjs +153 -15
- package/dist/federation/temporal.test.d.mts +2 -0
- package/dist/federation/temporal.test.mjs +71 -0
- package/dist/federation/webfinger.test.mjs +150 -5
- package/dist/{http-B4rrzxtg.js → http-B47Jg9iX.js} +994 -64
- package/dist/{http-CSlLA54v.mjs → http-CO59u1Yk.mjs} +146 -47
- package/dist/{http-BrGqJDXL.cjs → http-CjZwUPiT.cjs} +1099 -61
- package/dist/{http-aQzN9Ayi.d.ts → http-VyDTd4G3.d.cts} +15 -3
- package/dist/{http-CrGuipxe.d.cts → http-lf8Hsd91.d.ts} +15 -1
- package/dist/{key-Bzx--sHD.mjs → key-Dqdlo3mG.mjs} +43 -18
- package/dist/{kv-CbLNp3zQ.d.cts → kv-D6hNiMTK.d.ts} +1 -0
- package/dist/{kv-cache-DI-Sk7-I.cjs → kv-cache-BQ1-zDKX.cjs} +20 -3
- package/dist/{kv-cache-EMIqoIuB.js → kv-cache-Dh2ZH9Gr.js} +20 -3
- package/dist/{kv-cache-U__xU4qR.mjs → kv-cache-DhLkLv0P.mjs} +21 -3
- package/dist/{kv-GFYnFoOl.d.ts → kv-gJ8LYbxX.d.cts} +1 -3
- package/dist/ld-JbCByP5P.mjs +626 -0
- package/dist/metrics-ColY3QKj.mjs +815 -0
- package/dist/{middleware-zjZ6AFCW.mjs → middleware-Buqzuiaf.mjs} +1 -1
- package/dist/{middleware-An4DjES4.cjs → middleware-CWQ3Yi07.cjs} +2318 -623
- package/dist/{middleware-K1g6bEkV.mjs → middleware-Cg4EyWsq.mjs} +1639 -373
- package/dist/{middleware-nidH7VZH.js → middleware-iAJY2aU-.js} +2260 -556
- package/dist/{mod-CR8soWa9.d.ts → mod-B0hW12_O.d.cts} +26 -4
- package/dist/mod-C0F6kvgS.d.cts +182 -0
- package/dist/{mod-Cr3f-ACa.d.cts → mod-COIAjwRS.d.ts} +26 -2
- package/dist/{mod-CMEbIaNh.d.cts → mod-DFvNJcNb.d.ts} +56 -4
- package/dist/mod-vPYVoa5n.d.ts +182 -0
- package/dist/{mod-CLgIXe9w.d.ts → mod-yvIXFAEi.d.cts} +56 -6
- package/dist/mod.cjs +9 -6
- package/dist/mod.d.cts +12 -10
- package/dist/mod.d.ts +12 -12
- package/dist/mod.js +11 -11
- package/dist/mq-D-nlpY04.d.ts +208 -0
- package/dist/mq-D8uSFzxe.d.cts +208 -0
- package/dist/{negotiation-SQvQgUqe.mjs → negotiation-DDstyBvc.mjs} +29 -0
- package/dist/nodeinfo/client.test.mjs +4 -4
- package/dist/nodeinfo/handler.test.mjs +4 -4
- package/dist/nodeinfo/mod.d.cts +2 -1
- package/dist/nodeinfo/mod.d.ts +2 -3
- package/dist/nodeinfo/mod.js +3 -3
- package/dist/nodeinfo/types.test.mjs +3 -3
- package/dist/otel/exporter.test.mjs +28 -25
- package/dist/otel/mod.cjs +6 -5
- package/dist/otel/mod.d.cts +5 -3
- package/dist/otel/mod.d.ts +5 -5
- package/dist/otel/mod.js +8 -7
- package/dist/{outgoing-jsonld-CNmZLixq.mjs → outgoing-jsonld-L_DbOaFe.mjs} +2 -2
- package/dist/{owner-C-tqfvxn.mjs → owner-B4LgOmzV.mjs} +2 -2
- package/dist/{owner-CptqhsOy.d.cts → owner-CnngXDNJ.d.ts} +2 -1
- package/dist/{owner-74ARJ5TL.d.ts → owner-DEvZuyOE.d.cts} +2 -3
- package/dist/{proof-BlmRPzrK.mjs → proof-BoSGOq9q.mjs} +26 -8
- package/dist/{proof-Bbbwf8_x.js → proof-CzhZuawt.js} +378 -15
- package/dist/{proof-BW89QMVB.cjs → proof-DvMI-3vq.cjs} +432 -15
- package/dist/runtime/mod.d.cts +1 -0
- package/dist/runtime/mod.d.ts +1 -2
- package/dist/runtime/mod.js +3 -3
- package/dist/{send-05pJLcb6.mjs → send-ByK1Q7DK.mjs} +72 -26
- package/dist/sig/http.test.mjs +356 -33
- package/dist/sig/key.test.mjs +103 -6
- package/dist/sig/ld.test.mjs +696 -7
- package/dist/sig/mod.cjs +2 -2
- package/dist/sig/mod.d.cts +4 -3
- package/dist/sig/mod.d.ts +4 -5
- package/dist/sig/mod.js +4 -4
- package/dist/sig/owner.test.mjs +6 -6
- package/dist/sig/proof.test.mjs +169 -8
- package/dist/{std__assert-CRDpx_HF.mjs → std__assert-BBjXFNOb.mjs} +5 -30
- package/dist/temporal-LpjyAjKb.mjs +95 -0
- package/dist/testing/mod.d.mts +110 -10
- package/dist/testing/mod.mjs +1 -2
- package/dist/{transformers-ve6e2xcg.js → transformers-BGMIq1cs.js} +2 -2
- package/dist/{types-hvL8ElAs.js → types-CAY3OdLq.js} +2 -2
- package/dist/utils/docloader.test.mjs +6 -6
- package/dist/utils/kv-cache.test.mjs +67 -2
- package/dist/utils/mod.cjs +1 -1
- package/dist/utils/mod.d.cts +2 -1
- package/dist/utils/mod.d.ts +2 -3
- package/dist/utils/mod.js +3 -3
- package/dist/vocab/cjs.test.mjs +1 -1
- package/dist/vocab/mod.d.cts +1 -0
- package/dist/vocab/mod.d.ts +1 -2
- package/dist/vocab/mod.js +2 -2
- package/package.json +16 -16
- package/dist/ld-DR78beiv.mjs +0 -279
- package/dist/middleware-BFBaR0mF.cjs +0 -4
- package/dist/mod-2d12ffz3.d.ts +0 -64
- package/dist/mod-D35TRn09.d.cts +0 -62
- package/dist/router-CrMLXoOr.mjs +0 -114
- package/dist/{activity-listener-ell7W1s9.mjs → activity-listener-tztVvlNb.mjs} +0 -0
- package/dist/{assert_equals-Ew3jOFa3.mjs → assert_equals-C-ZRDbaf.mjs} +0 -0
- package/dist/{client-D_1QpnWt.mjs → client-ByXmQhYD.mjs} +1 -1
- package/dist/{collection-D-HqUuA2.mjs → collection-Cc3DVAhE.mjs} +0 -0
- package/dist/{keycache-EGATflN-.mjs → keycache-BeU0LCII.mjs} +0 -0
- package/dist/{public-audience-DYFHzm_c.mjs → public-audience-Cvbr2Gzt.mjs} +1 -1
- /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-
|
|
5
|
-
import { t as assertEquals } from "../assert_equals-
|
|
6
|
-
import "../std__assert-
|
|
7
|
-
import { n as assertFalse, t as assertRejects } from "../assert_rejects-
|
|
8
|
-
import { t as assertInstanceOf } from "../assert_instance_of-
|
|
9
|
-
import { t as assertNotEquals } from "../assert_not_equals
|
|
10
|
-
import { t as assert } from "../assert-
|
|
11
|
-
import { l as verifyRequest } from "../http-
|
|
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-
|
|
14
|
-
import { n as extractInboxes, r as sendActivity, t as SendActivityError } from "../send-
|
|
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
|
-
|
|
411
|
-
|
|
412
|
-
assertEquals(
|
|
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,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-
|
|
5
|
-
import { t as assertEquals } from "../assert_equals-
|
|
6
|
-
import "../std__assert-
|
|
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-
|
|
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 {};
|