@fedify/fedify 2.0.35-dev.2470 → 2.0.35-dev.2485
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/dist/{builder-CusH9K0G.mjs → builder-CKbQrOH1.mjs} +3 -3
- package/dist/{client-CAM_bQXx.d.cts → client-BIBzqEIp.d.cts} +0 -1
- package/dist/{client-BUqbea1d.mjs → client-DGNacEx_.mjs} +113 -64
- package/dist/{client-CSddvgWN.d.ts → client-jE7oGvRh.d.ts} +0 -1
- package/dist/compat/mod.d.cts +1 -1
- package/dist/compat/mod.d.ts +1 -1
- package/dist/compat/transformers.test.mjs +1 -1
- package/dist/{context-r8sjhltN.d.cts → context-CnoWxuoB.d.cts} +2 -2
- package/dist/{context-B2iX3heK.d.ts → context-lxjaUTAI.d.ts} +2 -2
- package/dist/{deno-BtRoR9in.mjs → deno-DL_ifPJk.mjs} +1 -1
- package/dist/docloader-B0_0Fjqo.mjs +124 -0
- package/dist/federation/builder.test.mjs +1 -1
- package/dist/federation/handler.test.mjs +6 -6
- package/dist/federation/idempotency.test.mjs +2 -2
- package/dist/federation/inbox.test.mjs +1 -1
- package/dist/federation/middleware.test.mjs +42 -23
- package/dist/federation/mod.cjs +1 -1
- package/dist/federation/mod.d.cts +2 -2
- package/dist/federation/mod.d.ts +2 -2
- package/dist/federation/mod.js +1 -1
- package/dist/federation/send.test.mjs +445 -70
- package/dist/federation/temporal.test.mjs +1 -1
- package/dist/federation/webfinger.test.mjs +1 -1
- package/dist/{http-Bs6thuCC.d.cts → http-Cd8UD1ym.d.ts} +1 -2
- package/dist/{http-C7d5tddX.cjs → http-CjkqnlR2.cjs} +114 -56
- package/dist/{http-CDupyfOc.d.ts → http-DFqAb11b.d.cts} +1 -2
- package/dist/{http-BTQ5wllw.js → http-DVEBwq6e.js} +113 -55
- package/dist/{http-CMduEDST.mjs → http-Dvsq4uCu.mjs} +136 -49
- package/dist/{inbox-BkvNt_Sb.mjs → inbox-D6YbZFrZ.mjs} +1 -1
- package/dist/{key-CDAqlyxF.mjs → key-CgcWGw6W.mjs} +1 -1
- package/dist/{kv-cache-Cm0DSKFo.js → kv-cache-DC_F2WSH.js} +60 -18
- package/dist/{kv-cache-D-xcxGwU.cjs → kv-cache-nN9RaiYp.cjs} +59 -17
- package/dist/{ld-Ceiqwon2.mjs → ld-CfOIwHup.mjs} +2 -2
- package/dist/{middleware-NVjGfqSC.js → middleware-B0eqDUmD.js} +118 -33
- package/dist/{middleware-Bj0R0_D7.mjs → middleware-CA7egMDR.mjs} +12 -12
- package/dist/{middleware-Bx0vwC0P.cjs → middleware-Cu_yKroA.cjs} +1 -1
- package/dist/{middleware-bv6duFIF.mjs → middleware-DEwj7jGj.mjs} +1 -1
- package/dist/{middleware-BWtJCObq.cjs → middleware-DZvWX5af.cjs} +118 -33
- package/dist/{mod-BkPB4hPe.d.cts → mod-BPDfu4Ey.d.cts} +2 -7
- package/dist/{mod-E7YM72JZ.d.ts → mod-BS-jg3Wx.d.ts} +1 -1
- package/dist/{mod-BKJyD7nk.d.ts → mod-DGP89q0u.d.ts} +2 -7
- package/dist/{mod-IOWUF5Yr.d.cts → mod-DVPS4yam.d.cts} +1 -1
- package/dist/{mod-CM_eR8hQ.d.ts → mod-I-34OMN3.d.ts} +1 -1
- package/dist/{mod-DDTLbfce.d.cts → mod-w1oyHseQ.d.cts} +1 -1
- package/dist/mod.cjs +5 -5
- package/dist/mod.d.cts +6 -6
- package/dist/mod.d.ts +6 -6
- package/dist/mod.js +5 -5
- package/dist/nodeinfo/client.test.mjs +337 -19
- package/dist/nodeinfo/handler.test.mjs +1 -1
- package/dist/nodeinfo/mod.cjs +1 -1
- package/dist/nodeinfo/mod.d.cts +1 -1
- package/dist/nodeinfo/mod.d.ts +1 -1
- package/dist/nodeinfo/mod.js +1 -1
- package/dist/otel/exporter.test.mjs +292 -30
- package/dist/otel/mod.cjs +36 -30
- package/dist/otel/mod.d.cts +13 -0
- package/dist/otel/mod.d.ts +13 -0
- package/dist/otel/mod.js +36 -30
- package/dist/{owner-Cmn2WKBi.mjs → owner-BvVwCoj2.mjs} +2 -2
- package/dist/{proof-DllVQdfR.cjs → proof-BssSZpMi.cjs} +1 -1
- package/dist/{proof-VZ9Ryp3F.mjs → proof-C9TBSmUR.mjs} +2 -2
- package/dist/{proof-ClDA_fq4.js → proof-Cp62GFJ3.js} +1 -1
- package/dist/{send-BiC5gxMS.mjs → send-ZWmjk2Zf.mjs} +117 -32
- package/dist/sig/http.test.mjs +411 -2
- package/dist/sig/key.test.mjs +2 -2
- package/dist/sig/ld.test.mjs +2 -2
- package/dist/sig/mod.cjs +2 -2
- package/dist/sig/mod.d.cts +2 -2
- package/dist/sig/mod.d.ts +2 -2
- package/dist/sig/mod.js +2 -2
- package/dist/sig/owner.test.mjs +1 -1
- package/dist/sig/proof.test.mjs +1 -1
- package/dist/{temporal-DXm8zGR8.mjs → temporal-BNtFi_ut.mjs} +1 -1
- package/dist/testing/mod.d.mts +0 -1
- package/dist/{types-miAYfiGO.js → types-B1_pRFE_.js} +112 -63
- package/dist/{types-DRPjmHVV.cjs → types-DOvyymM4.cjs} +111 -62
- package/dist/utils/docloader.test.mjs +316 -32
- package/dist/utils/mod.cjs +1 -1
- package/dist/utils/mod.d.cts +1 -1
- package/dist/utils/mod.d.ts +1 -1
- package/dist/utils/mod.js +1 -1
- package/package.json +6 -6
- package/dist/docloader-BF0FJHJ1.mjs +0 -82
|
@@ -5,12 +5,21 @@ import { t as esm_default } from "../esm-DVILvP5e.mjs";
|
|
|
5
5
|
import { t as assertEquals } from "../assert_equals-Ew3jOFa3.mjs";
|
|
6
6
|
import "../std__assert-DflMZha5.mjs";
|
|
7
7
|
import { n as assertInstanceOf } from "../assert-BhQd8QHP.mjs";
|
|
8
|
-
import { a as
|
|
8
|
+
import { a as parseOutboundService, c as parseSoftware, i as parseNodeInfo, l as parseUsage, n as getNodeInfoInternal, o as parseProtocol, r as parseInboundService, s as parseServices, t as getNodeInfo } from "../client-DGNacEx_.mjs";
|
|
9
9
|
import { test } from "@fedify/fixture";
|
|
10
10
|
import { UrlError } from "@fedify/vocab-runtime";
|
|
11
|
+
import { openDirectFetch, openPublicFetch } from "@fedify/vocab-runtime/internal";
|
|
11
12
|
import { configure, reset } from "@logtape/logtape";
|
|
12
|
-
import dns from "node:dns/promises";
|
|
13
13
|
//#region src/nodeinfo/client.test.ts
|
|
14
|
+
function getNodeInfoDirect(url, options) {
|
|
15
|
+
return getNodeInfoInternal(url, options, { openHop: openDirectFetch });
|
|
16
|
+
}
|
|
17
|
+
function getNodeInfoValidated(url, options) {
|
|
18
|
+
return getNodeInfoInternal(url, options, { openHop: (hopUrl) => new URL(hopUrl).hostname === "example.com" ? openDirectFetch(hopUrl) : openPublicFetch(hopUrl, { lookup: () => Promise.resolve([{
|
|
19
|
+
address: "93.184.215.14",
|
|
20
|
+
family: 4
|
|
21
|
+
}]) }) });
|
|
22
|
+
}
|
|
14
23
|
test("getNodeInfo()", async (t) => {
|
|
15
24
|
esm_default.spyGlobal();
|
|
16
25
|
esm_default.get("https://example.com/.well-known/nodeinfo", { body: { links: [{
|
|
@@ -44,25 +53,25 @@ test("getNodeInfo()", async (t) => {
|
|
|
44
53
|
}
|
|
45
54
|
};
|
|
46
55
|
await t.step("indirect", async () => {
|
|
47
|
-
assertEquals(await
|
|
48
|
-
assertEquals(await
|
|
56
|
+
assertEquals(await getNodeInfoDirect("https://example.com/"), expected);
|
|
57
|
+
assertEquals(await getNodeInfoDirect("https://example.com/", { parse: "none" }), rawExpected);
|
|
49
58
|
});
|
|
50
59
|
await t.step("direct", async () => {
|
|
51
|
-
assertEquals(await
|
|
60
|
+
assertEquals(await getNodeInfoDirect("https://example.com/nodeinfo/2.1", { direct: true }), expected);
|
|
52
61
|
});
|
|
53
62
|
esm_default.removeRoutes();
|
|
54
63
|
esm_default.get("https://example.com/.well-known/nodeinfo", { body: { links: [] } });
|
|
55
64
|
await t.step("indirect: no links", async () => {
|
|
56
|
-
assertEquals(await
|
|
65
|
+
assertEquals(await getNodeInfoDirect("https://example.com/"), void 0);
|
|
57
66
|
});
|
|
58
67
|
esm_default.removeRoutes();
|
|
59
68
|
esm_default.get("https://example.com/.well-known/nodeinfo", { status: 404 });
|
|
60
69
|
await t.step("indirect: 404", async () => {
|
|
61
|
-
assertEquals(await
|
|
70
|
+
assertEquals(await getNodeInfoDirect("https://example.com/"), void 0);
|
|
62
71
|
});
|
|
63
72
|
await t.step("direct: 404", async () => {
|
|
64
|
-
assertEquals(await
|
|
65
|
-
assertEquals(await
|
|
73
|
+
assertEquals(await getNodeInfoDirect("https://example.com/nodeinfo/2.0", { direct: true }), void 0);
|
|
74
|
+
assertEquals(await getNodeInfoDirect("https://example.com/404", { direct: true }), void 0);
|
|
66
75
|
});
|
|
67
76
|
esm_default.hardReset();
|
|
68
77
|
});
|
|
@@ -79,7 +88,7 @@ test("getNodeInfo() blocks SSRF", async (t) => {
|
|
|
79
88
|
internalFetched = true;
|
|
80
89
|
return { body: { secret: "INTERNAL_DATA_LEAKED" } };
|
|
81
90
|
});
|
|
82
|
-
assertEquals(await
|
|
91
|
+
assertEquals(await getNodeInfoValidated("https://example.com/", { parse: "none" }), void 0);
|
|
83
92
|
assertEquals(internalFetched, false);
|
|
84
93
|
});
|
|
85
94
|
await t.step("link href using a data: URL", async () => {
|
|
@@ -88,7 +97,7 @@ test("getNodeInfo() blocks SSRF", async (t) => {
|
|
|
88
97
|
rel: "http://nodeinfo.diaspora.software/ns/schema/2.0",
|
|
89
98
|
href: "data:application/json,{\"software\":{\"name\":\"x\"}}"
|
|
90
99
|
}] } });
|
|
91
|
-
assertEquals(await
|
|
100
|
+
assertEquals(await getNodeInfoValidated("https://example.com/", { parse: "none" }), void 0);
|
|
92
101
|
});
|
|
93
102
|
await t.step("redirect to a private address", async () => {
|
|
94
103
|
esm_default.removeRoutes();
|
|
@@ -105,7 +114,7 @@ test("getNodeInfo() blocks SSRF", async (t) => {
|
|
|
105
114
|
internalFetched = true;
|
|
106
115
|
return { body: { secret: "INTERNAL_DATA_LEAKED" } };
|
|
107
116
|
});
|
|
108
|
-
assertEquals(await
|
|
117
|
+
assertEquals(await getNodeInfoValidated("https://example.com/", { parse: "none" }), void 0);
|
|
109
118
|
assertEquals(internalFetched, false);
|
|
110
119
|
});
|
|
111
120
|
await t.step("direct fetch of a private address", async () => {
|
|
@@ -118,7 +127,7 @@ test("getNodeInfo() blocks SSRF", async (t) => {
|
|
|
118
127
|
version: "1.0.0"
|
|
119
128
|
} } };
|
|
120
129
|
});
|
|
121
|
-
assertEquals(await
|
|
130
|
+
assertEquals(await getNodeInfoValidated("http://127.0.0.1:8080/nodeinfo", {
|
|
122
131
|
direct: true,
|
|
123
132
|
parse: "none"
|
|
124
133
|
}), void 0);
|
|
@@ -998,17 +1007,16 @@ test("getNodeInfo() bounds both discovery and document responses", async () => {
|
|
|
998
1007
|
esm_default.hardReset();
|
|
999
1008
|
}
|
|
1000
1009
|
});
|
|
1001
|
-
test("getNodeInfo() logs DNS failures as such",
|
|
1010
|
+
test("getNodeInfo() logs DNS failures as such", async (t) => {
|
|
1002
1011
|
for (const result of [
|
|
1003
1012
|
"throws",
|
|
1004
1013
|
"empty",
|
|
1005
1014
|
"private"
|
|
1006
1015
|
]) await t.step(result, async () => {
|
|
1007
|
-
const
|
|
1008
|
-
dns.lookup = (() => result === "throws" ? Promise.reject(/* @__PURE__ */ new Error("Resolver unavailable")) : Promise.resolve(result === "empty" ? [] : [{
|
|
1016
|
+
const lookup = () => result === "throws" ? Promise.reject(/* @__PURE__ */ new Error("Resolver unavailable")) : Promise.resolve(result === "empty" ? [] : [{
|
|
1009
1017
|
address: "127.0.0.1",
|
|
1010
1018
|
family: 4
|
|
1011
|
-
}])
|
|
1019
|
+
}]);
|
|
1012
1020
|
const records = [];
|
|
1013
1021
|
await configure({
|
|
1014
1022
|
sinks: { buffer: (record) => records.push(record) },
|
|
@@ -1023,7 +1031,7 @@ test("getNodeInfo() logs DNS failures as such", { ignore: "Deno" in globalThis &
|
|
|
1023
1031
|
reset: true
|
|
1024
1032
|
});
|
|
1025
1033
|
try {
|
|
1026
|
-
assertEquals(await
|
|
1034
|
+
assertEquals(await getNodeInfoInternal("https://dns-failure.invalid/", {}, { openHop: (url) => openPublicFetch(url, { lookup }) }), void 0);
|
|
1027
1035
|
assertEquals(records.map((r) => [
|
|
1028
1036
|
r.level,
|
|
1029
1037
|
r.rawMessage,
|
|
@@ -1038,9 +1046,319 @@ test("getNodeInfo() logs DNS failures as such", { ignore: "Deno" in globalThis &
|
|
|
1038
1046
|
assertEquals(error.reason, result === "private" ? "disallowed" : "dns");
|
|
1039
1047
|
} finally {
|
|
1040
1048
|
await reset();
|
|
1041
|
-
dns.lookup = originalLookup;
|
|
1042
1049
|
}
|
|
1043
1050
|
});
|
|
1044
1051
|
});
|
|
1052
|
+
function recordHops(respond) {
|
|
1053
|
+
const events = [];
|
|
1054
|
+
const unsettled = [];
|
|
1055
|
+
let opens = 0;
|
|
1056
|
+
let closes = 0;
|
|
1057
|
+
let responses = 0;
|
|
1058
|
+
return {
|
|
1059
|
+
events,
|
|
1060
|
+
async openHop(url) {
|
|
1061
|
+
opens++;
|
|
1062
|
+
events.push(`open ${url}`);
|
|
1063
|
+
const states = [];
|
|
1064
|
+
let closed = false;
|
|
1065
|
+
return {
|
|
1066
|
+
url: new URL(url),
|
|
1067
|
+
async fetch(input) {
|
|
1068
|
+
if (closed) throw new TypeError("The fetch session is closed.");
|
|
1069
|
+
const target = new URL(input instanceof Request ? input.url : input);
|
|
1070
|
+
const id = ++responses;
|
|
1071
|
+
events.push(`fetch ${target.href} #${id}`);
|
|
1072
|
+
const fake = respond(target);
|
|
1073
|
+
const state = {
|
|
1074
|
+
id,
|
|
1075
|
+
settled: false
|
|
1076
|
+
};
|
|
1077
|
+
states.push(state);
|
|
1078
|
+
let sent = false;
|
|
1079
|
+
const body = new ReadableStream({
|
|
1080
|
+
pull(controller) {
|
|
1081
|
+
if (!sent && fake.body != null && fake.body !== "") {
|
|
1082
|
+
sent = true;
|
|
1083
|
+
controller.enqueue(new TextEncoder().encode(fake.body));
|
|
1084
|
+
return;
|
|
1085
|
+
}
|
|
1086
|
+
state.settled = true;
|
|
1087
|
+
events.push(`consumed #${id}`);
|
|
1088
|
+
controller.close();
|
|
1089
|
+
},
|
|
1090
|
+
cancel() {
|
|
1091
|
+
state.settled = true;
|
|
1092
|
+
events.push(`cancel #${id}`);
|
|
1093
|
+
if (fake.cancelFails) throw new Error("Cancellation failed");
|
|
1094
|
+
if (fake.cancelNeverSettles) return new Promise(() => {});
|
|
1095
|
+
}
|
|
1096
|
+
}, { highWaterMark: 0 });
|
|
1097
|
+
const response = new Response(body, {
|
|
1098
|
+
status: fake.status ?? 200,
|
|
1099
|
+
headers: fake.headers
|
|
1100
|
+
});
|
|
1101
|
+
if (fake.url != null) Object.defineProperty(response, "url", { value: fake.url });
|
|
1102
|
+
return response;
|
|
1103
|
+
},
|
|
1104
|
+
async close() {
|
|
1105
|
+
if (closed) return;
|
|
1106
|
+
closed = true;
|
|
1107
|
+
closes++;
|
|
1108
|
+
for (const state of states) if (!state.settled) unsettled.push(`#${state.id}`);
|
|
1109
|
+
events.push(`close ${url}`);
|
|
1110
|
+
}
|
|
1111
|
+
};
|
|
1112
|
+
},
|
|
1113
|
+
assertInvariants() {
|
|
1114
|
+
assertEquals(closes, opens);
|
|
1115
|
+
assertEquals(unsettled, []);
|
|
1116
|
+
}
|
|
1117
|
+
};
|
|
1118
|
+
}
|
|
1119
|
+
test("getNodeInfo() closes each hop after cleaning up its body", async (t) => {
|
|
1120
|
+
const wellKnown = "https://example.com/.well-known/nodeinfo";
|
|
1121
|
+
const wellKnown2 = "https://example.com/.well-known/nodeinfo2";
|
|
1122
|
+
const documentUrl = "https://example.com/nodeinfo/2.1";
|
|
1123
|
+
const rawDocument = {
|
|
1124
|
+
software: {
|
|
1125
|
+
name: "foo",
|
|
1126
|
+
version: "1.2.3"
|
|
1127
|
+
},
|
|
1128
|
+
protocols: ["activitypub"],
|
|
1129
|
+
usage: {
|
|
1130
|
+
users: {},
|
|
1131
|
+
localPosts: 0,
|
|
1132
|
+
localComments: 0
|
|
1133
|
+
}
|
|
1134
|
+
};
|
|
1135
|
+
const descriptor = JSON.stringify({ links: [{
|
|
1136
|
+
rel: "http://nodeinfo.diaspora.software/ns/schema/2.1",
|
|
1137
|
+
href: documentUrl
|
|
1138
|
+
}] });
|
|
1139
|
+
await t.step("redirect", async () => {
|
|
1140
|
+
const recorder = recordHops((url) => {
|
|
1141
|
+
switch (url.href) {
|
|
1142
|
+
case wellKnown: return {
|
|
1143
|
+
status: 302,
|
|
1144
|
+
headers: { Location: "/.well-known/nodeinfo2" },
|
|
1145
|
+
body: "redirecting",
|
|
1146
|
+
url: "https://93.184.215.14/.well-known/nodeinfo"
|
|
1147
|
+
};
|
|
1148
|
+
case wellKnown2: return { body: descriptor };
|
|
1149
|
+
default: return { body: JSON.stringify(rawDocument) };
|
|
1150
|
+
}
|
|
1151
|
+
});
|
|
1152
|
+
assertEquals(await getNodeInfoInternal("https://example.com/", { parse: "none" }, { openHop: recorder.openHop }), rawDocument);
|
|
1153
|
+
assertEquals(recorder.events, [
|
|
1154
|
+
`open ${wellKnown}`,
|
|
1155
|
+
`fetch ${wellKnown} #1`,
|
|
1156
|
+
"cancel #1",
|
|
1157
|
+
`close ${wellKnown}`,
|
|
1158
|
+
`open ${wellKnown2}`,
|
|
1159
|
+
`fetch ${wellKnown2} #2`,
|
|
1160
|
+
"consumed #2",
|
|
1161
|
+
`close ${wellKnown2}`,
|
|
1162
|
+
`open ${documentUrl}`,
|
|
1163
|
+
`fetch ${documentUrl} #3`,
|
|
1164
|
+
"consumed #3",
|
|
1165
|
+
`close ${documentUrl}`
|
|
1166
|
+
]);
|
|
1167
|
+
recorder.assertInvariants();
|
|
1168
|
+
});
|
|
1169
|
+
await t.step("non-2xx responses", async () => {
|
|
1170
|
+
const recorder = recordHops(() => ({
|
|
1171
|
+
status: 404,
|
|
1172
|
+
body: "not found"
|
|
1173
|
+
}));
|
|
1174
|
+
assertEquals(await getNodeInfoInternal("https://example.com/", {}, { openHop: recorder.openHop }), void 0);
|
|
1175
|
+
assertEquals(await getNodeInfoInternal(documentUrl, { direct: true }, { openHop: recorder.openHop }), void 0);
|
|
1176
|
+
assertEquals(recorder.events, [
|
|
1177
|
+
`open ${wellKnown}`,
|
|
1178
|
+
`fetch ${wellKnown} #1`,
|
|
1179
|
+
"cancel #1",
|
|
1180
|
+
`close ${wellKnown}`,
|
|
1181
|
+
`open ${documentUrl}`,
|
|
1182
|
+
`fetch ${documentUrl} #2`,
|
|
1183
|
+
"cancel #2",
|
|
1184
|
+
`close ${documentUrl}`
|
|
1185
|
+
]);
|
|
1186
|
+
recorder.assertInvariants();
|
|
1187
|
+
});
|
|
1188
|
+
await t.step("too many redirections", async () => {
|
|
1189
|
+
const recorder = recordHops(() => ({
|
|
1190
|
+
status: 302,
|
|
1191
|
+
headers: { Location: "/.well-known/nodeinfo" },
|
|
1192
|
+
body: "redirecting"
|
|
1193
|
+
}));
|
|
1194
|
+
assertEquals(await getNodeInfoInternal("https://example.com/", {}, { openHop: recorder.openHop }), void 0);
|
|
1195
|
+
assertEquals(recorder.events.filter((e) => e.startsWith("open ")).length, 5);
|
|
1196
|
+
assertEquals(recorder.events.filter((e) => e.startsWith("cancel ")).length, 5);
|
|
1197
|
+
recorder.assertInvariants();
|
|
1198
|
+
});
|
|
1199
|
+
await t.step("failed redirect body cancellation", async () => {
|
|
1200
|
+
const recorder = recordHops((url) => {
|
|
1201
|
+
switch (url.href) {
|
|
1202
|
+
case wellKnown: return {
|
|
1203
|
+
status: 302,
|
|
1204
|
+
headers: { Location: "/.well-known/nodeinfo2" },
|
|
1205
|
+
body: "redirecting",
|
|
1206
|
+
cancelFails: true
|
|
1207
|
+
};
|
|
1208
|
+
case wellKnown2: return { body: descriptor };
|
|
1209
|
+
default: return {
|
|
1210
|
+
status: 404,
|
|
1211
|
+
body: "not found",
|
|
1212
|
+
cancelFails: true
|
|
1213
|
+
};
|
|
1214
|
+
}
|
|
1215
|
+
});
|
|
1216
|
+
const records = [];
|
|
1217
|
+
await configure({
|
|
1218
|
+
sinks: { buffer: (record) => records.push(record) },
|
|
1219
|
+
loggers: [{
|
|
1220
|
+
category: "fedify",
|
|
1221
|
+
sinks: ["buffer"],
|
|
1222
|
+
lowestLevel: "debug"
|
|
1223
|
+
}, {
|
|
1224
|
+
category: ["logtape", "meta"],
|
|
1225
|
+
sinks: []
|
|
1226
|
+
}],
|
|
1227
|
+
reset: true
|
|
1228
|
+
});
|
|
1229
|
+
try {
|
|
1230
|
+
assertEquals(await getNodeInfoInternal("https://example.com/", {}, { openHop: recorder.openHop }), void 0);
|
|
1231
|
+
const failures = records.filter((r) => r.rawMessage === "Failed to cancel the response body from {url}: {error}");
|
|
1232
|
+
assertEquals(failures.map((r) => [r.level, r.properties.url]), [["debug", wellKnown], ["debug", documentUrl]]);
|
|
1233
|
+
for (const { properties: { error } } of failures) {
|
|
1234
|
+
assertInstanceOf(error, Error);
|
|
1235
|
+
assertEquals(error.message, "Cancellation failed");
|
|
1236
|
+
}
|
|
1237
|
+
} finally {
|
|
1238
|
+
await reset();
|
|
1239
|
+
}
|
|
1240
|
+
assertEquals(recorder.events, [
|
|
1241
|
+
`open ${wellKnown}`,
|
|
1242
|
+
`fetch ${wellKnown} #1`,
|
|
1243
|
+
"cancel #1",
|
|
1244
|
+
`close ${wellKnown}`,
|
|
1245
|
+
`open ${wellKnown2}`,
|
|
1246
|
+
`fetch ${wellKnown2} #2`,
|
|
1247
|
+
"consumed #2",
|
|
1248
|
+
`close ${wellKnown2}`,
|
|
1249
|
+
`open ${documentUrl}`,
|
|
1250
|
+
`fetch ${documentUrl} #3`,
|
|
1251
|
+
"cancel #3",
|
|
1252
|
+
`close ${documentUrl}`
|
|
1253
|
+
]);
|
|
1254
|
+
recorder.assertInvariants();
|
|
1255
|
+
});
|
|
1256
|
+
await t.step("never-settling body cancellation", async () => {
|
|
1257
|
+
const recorder = recordHops((url) => {
|
|
1258
|
+
switch (url.href) {
|
|
1259
|
+
case wellKnown: return {
|
|
1260
|
+
status: 302,
|
|
1261
|
+
headers: { Location: "/.well-known/nodeinfo2" },
|
|
1262
|
+
body: "redirecting",
|
|
1263
|
+
cancelNeverSettles: true
|
|
1264
|
+
};
|
|
1265
|
+
case wellKnown2: return { body: descriptor };
|
|
1266
|
+
default: return {
|
|
1267
|
+
status: 404,
|
|
1268
|
+
body: "not found",
|
|
1269
|
+
cancelNeverSettles: true
|
|
1270
|
+
};
|
|
1271
|
+
}
|
|
1272
|
+
});
|
|
1273
|
+
assertEquals(await settleWithin(getNodeInfoInternal("https://example.com/", {}, { openHop: recorder.openHop })), void 0);
|
|
1274
|
+
assertEquals(recorder.events, [
|
|
1275
|
+
`open ${wellKnown}`,
|
|
1276
|
+
`fetch ${wellKnown} #1`,
|
|
1277
|
+
"cancel #1",
|
|
1278
|
+
`close ${wellKnown}`,
|
|
1279
|
+
`open ${wellKnown2}`,
|
|
1280
|
+
`fetch ${wellKnown2} #2`,
|
|
1281
|
+
"consumed #2",
|
|
1282
|
+
`close ${wellKnown2}`,
|
|
1283
|
+
`open ${documentUrl}`,
|
|
1284
|
+
`fetch ${documentUrl} #3`,
|
|
1285
|
+
"cancel #3",
|
|
1286
|
+
`close ${documentUrl}`
|
|
1287
|
+
]);
|
|
1288
|
+
recorder.assertInvariants();
|
|
1289
|
+
});
|
|
1290
|
+
await t.step("failure while consuming", async () => {
|
|
1291
|
+
const recorder = recordHops(() => ({ body: "not json" }));
|
|
1292
|
+
assertEquals(await getNodeInfoInternal("https://example.com/", {}, { openHop: recorder.openHop }), void 0);
|
|
1293
|
+
assertEquals(recorder.events, [
|
|
1294
|
+
`open ${wellKnown}`,
|
|
1295
|
+
`fetch ${wellKnown} #1`,
|
|
1296
|
+
"consumed #1",
|
|
1297
|
+
`close ${wellKnown}`
|
|
1298
|
+
]);
|
|
1299
|
+
recorder.assertInvariants();
|
|
1300
|
+
});
|
|
1301
|
+
});
|
|
1302
|
+
/**
|
|
1303
|
+
* Rejects if the given promise does not settle within the timeout, so that
|
|
1304
|
+
* a hang fails the test instead of blocking it.
|
|
1305
|
+
*/
|
|
1306
|
+
async function settleWithin(promise, ms = 5e3) {
|
|
1307
|
+
let timer;
|
|
1308
|
+
try {
|
|
1309
|
+
return await Promise.race([promise, new Promise((_, reject) => {
|
|
1310
|
+
timer = setTimeout(() => reject(/* @__PURE__ */ new Error(`Did not settle within ${ms} ms.`)), ms);
|
|
1311
|
+
})]);
|
|
1312
|
+
} finally {
|
|
1313
|
+
clearTimeout(timer);
|
|
1314
|
+
}
|
|
1315
|
+
}
|
|
1316
|
+
function neverCancellableResponse(init) {
|
|
1317
|
+
return new Response(new ReadableStream({
|
|
1318
|
+
start(controller) {
|
|
1319
|
+
controller.enqueue(new TextEncoder().encode("never cancelled"));
|
|
1320
|
+
},
|
|
1321
|
+
cancel: () => new Promise(() => {})
|
|
1322
|
+
}), init);
|
|
1323
|
+
}
|
|
1324
|
+
test("getNodeInfo() does not wait for cancelling response bodies", async (t) => {
|
|
1325
|
+
const rawDocument = {
|
|
1326
|
+
software: {
|
|
1327
|
+
name: "foo",
|
|
1328
|
+
version: "1.2.3"
|
|
1329
|
+
},
|
|
1330
|
+
protocols: ["activitypub"],
|
|
1331
|
+
usage: {
|
|
1332
|
+
users: {},
|
|
1333
|
+
localPosts: 0,
|
|
1334
|
+
localComments: 0
|
|
1335
|
+
}
|
|
1336
|
+
};
|
|
1337
|
+
esm_default.mockGlobal();
|
|
1338
|
+
try {
|
|
1339
|
+
await t.step("redirect", async () => {
|
|
1340
|
+
esm_default.removeRoutes();
|
|
1341
|
+
esm_default.get("https://example.com/.well-known/nodeinfo", () => neverCancellableResponse({
|
|
1342
|
+
status: 302,
|
|
1343
|
+
headers: { Location: "/.well-known/nodeinfo2" }
|
|
1344
|
+
}));
|
|
1345
|
+
esm_default.get("https://example.com/.well-known/nodeinfo2", () => Response.json({ links: [{
|
|
1346
|
+
rel: "http://nodeinfo.diaspora.software/ns/schema/2.1",
|
|
1347
|
+
href: "https://example.com/nodeinfo/2.1"
|
|
1348
|
+
}] }));
|
|
1349
|
+
esm_default.get("https://example.com/nodeinfo/2.1", () => Response.json(rawDocument));
|
|
1350
|
+
assertEquals(await settleWithin(getNodeInfoDirect("https://example.com/", { parse: "none" })), rawDocument);
|
|
1351
|
+
});
|
|
1352
|
+
await t.step("non-2xx responses", async () => {
|
|
1353
|
+
esm_default.removeRoutes();
|
|
1354
|
+
esm_default.get("https://example.com/.well-known/nodeinfo", () => neverCancellableResponse({ status: 404 }));
|
|
1355
|
+
esm_default.get("https://example.com/nodeinfo/2.1", () => neverCancellableResponse({ status: 404 }));
|
|
1356
|
+
assertEquals(await settleWithin(getNodeInfoDirect("https://example.com/")), void 0);
|
|
1357
|
+
assertEquals(await settleWithin(getNodeInfoDirect("https://example.com/nodeinfo/2.1", { direct: true })), void 0);
|
|
1358
|
+
});
|
|
1359
|
+
} finally {
|
|
1360
|
+
esm_default.hardReset();
|
|
1361
|
+
}
|
|
1362
|
+
});
|
|
1045
1363
|
//#endregion
|
|
1046
1364
|
export {};
|
|
@@ -5,7 +5,7 @@ import { n as createRequestContext } from "../context-Juj6bdHC.mjs";
|
|
|
5
5
|
import { t as assertEquals } from "../assert_equals-Ew3jOFa3.mjs";
|
|
6
6
|
import "../std__assert-DflMZha5.mjs";
|
|
7
7
|
import { t as MemoryKvStore } from "../kv-mI4Ix2EU.mjs";
|
|
8
|
-
import { a as createFederation, h as handleNodeInfoJrd, m as handleNodeInfo } from "../middleware-
|
|
8
|
+
import { a as createFederation, h as handleNodeInfoJrd, m as handleNodeInfo } from "../middleware-CA7egMDR.mjs";
|
|
9
9
|
import { test } from "@fedify/fixture";
|
|
10
10
|
//#region src/nodeinfo/handler.test.ts
|
|
11
11
|
test("handleNodeInfo()", async () => {
|
package/dist/nodeinfo/mod.cjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
const { Temporal } = require("@js-temporal/polyfill");
|
|
2
2
|
const { URLPattern } = require("urlpattern-polyfill");
|
|
3
3
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
4
|
-
const require_types = require("../types-
|
|
4
|
+
const require_types = require("../types-DOvyymM4.cjs");
|
|
5
5
|
exports.getNodeInfo = require_types.getNodeInfo;
|
|
6
6
|
exports.nodeInfoToJson = require_types.nodeInfoToJson;
|
|
7
7
|
exports.parseNodeInfo = require_types.parseNodeInfo;
|
package/dist/nodeinfo/mod.d.cts
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
/// <reference lib="esnext.temporal" />
|
|
2
|
-
import { a as InboundService, c as OutboundService, d as Software, f as Usage, i as parseNodeInfo, l as Protocol, n as ParseNodeInfoOptions, o as JsonValue, p as nodeInfoToJson, r as getNodeInfo, s as NodeInfo, t as GetNodeInfoOptions, u as Services } from "../client-
|
|
2
|
+
import { a as InboundService, c as OutboundService, d as Software, f as Usage, i as parseNodeInfo, l as Protocol, n as ParseNodeInfoOptions, o as JsonValue, p as nodeInfoToJson, r as getNodeInfo, s as NodeInfo, t as GetNodeInfoOptions, u as Services } from "../client-BIBzqEIp.cjs";
|
|
3
3
|
export { type GetNodeInfoOptions, InboundService, JsonValue, NodeInfo, OutboundService, type ParseNodeInfoOptions, Protocol, Services, Software, Usage, getNodeInfo, nodeInfoToJson, parseNodeInfo };
|
package/dist/nodeinfo/mod.d.ts
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
/// <reference lib="esnext.temporal" />
|
|
2
|
-
import { a as InboundService, c as OutboundService, d as Software, f as Usage, i as parseNodeInfo, l as Protocol, n as ParseNodeInfoOptions, o as JsonValue, p as nodeInfoToJson, r as getNodeInfo, s as NodeInfo, t as GetNodeInfoOptions, u as Services } from "../client-
|
|
2
|
+
import { a as InboundService, c as OutboundService, d as Software, f as Usage, i as parseNodeInfo, l as Protocol, n as ParseNodeInfoOptions, o as JsonValue, p as nodeInfoToJson, r as getNodeInfo, s as NodeInfo, t as GetNodeInfoOptions, u as Services } from "../client-jE7oGvRh.js";
|
|
3
3
|
export { type GetNodeInfoOptions, InboundService, JsonValue, NodeInfo, OutboundService, type ParseNodeInfoOptions, Protocol, Services, Software, Usage, getNodeInfo, nodeInfoToJson, parseNodeInfo };
|
package/dist/nodeinfo/mod.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
import { Temporal } from "@js-temporal/polyfill";
|
|
2
2
|
import { URLPattern } from "urlpattern-polyfill";
|
|
3
|
-
import { n as getNodeInfo, r as parseNodeInfo, t as nodeInfoToJson } from "../types-
|
|
3
|
+
import { n as getNodeInfo, r as parseNodeInfo, t as nodeInfoToJson } from "../types-B1_pRFE_.js";
|
|
4
4
|
export { getNodeInfo, nodeInfoToJson, parseNodeInfo };
|