@fedify/vocab-runtime 2.4.0-pr.934.40 → 2.4.0

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 (140) hide show
  1. package/deno.json +4 -2
  2. package/dist/{tests/docloader-Xi61Le8w.mjs → contexts-BK1CqBR5.cjs} +164 -269
  3. package/dist/{tests/docloader-BFedvemb.cjs → contexts-DPuJ4UYL.js} +160 -288
  4. package/dist/{docloader-DnUMWHaJ.d.cts → docloader-CYwqh5Df.d.cts} +68 -2
  5. package/dist/{docloader-xRGn1azD.d.ts → docloader-CYwqh5Df.d.ts} +68 -2
  6. package/dist/internal/jsonld-cache.cjs +70 -1
  7. package/dist/internal/jsonld-cache.d.cts +35 -2
  8. package/dist/internal/jsonld-cache.d.ts +35 -2
  9. package/dist/internal/jsonld-cache.js +67 -2
  10. package/dist/internal/portable-dereference.cjs +721 -0
  11. package/dist/internal/portable-dereference.d.cts +349 -0
  12. package/dist/internal/portable-dereference.d.ts +349 -0
  13. package/dist/internal/portable-dereference.js +702 -0
  14. package/dist/internal/signed-representation.cjs +378 -0
  15. package/dist/internal/signed-representation.d.cts +144 -0
  16. package/dist/internal/signed-representation.d.ts +144 -0
  17. package/dist/internal/signed-representation.js +371 -0
  18. package/dist/jsonld.cjs +2 -2
  19. package/dist/mod.cjs +715 -4523
  20. package/dist/mod.d.cts +406 -6
  21. package/dist/mod.d.ts +406 -7
  22. package/dist/mod.js +694 -4518
  23. package/dist/portable-DqtfLy_1.d.cts +160 -0
  24. package/dist/portable-DtWsu2yU.d.ts +160 -0
  25. package/dist/tests/body-CYiu9CO-.mjs +130 -0
  26. package/dist/tests/body-CkEwROsn.cjs +153 -0
  27. package/dist/tests/body.test.cjs +89 -0
  28. package/dist/tests/body.test.d.cts +1 -0
  29. package/dist/tests/body.test.d.mts +1 -0
  30. package/dist/tests/body.test.mjs +90 -0
  31. package/dist/tests/contexts-CIKsin4e.mjs +4512 -0
  32. package/dist/tests/contexts-DizzBjz4.cjs +4523 -0
  33. package/dist/tests/decimal.test.cjs +8 -7
  34. package/dist/tests/decimal.test.mjs +8 -6
  35. package/dist/tests/digest-3FeH2Y-Q.cjs +176 -0
  36. package/dist/tests/digest-COC7xDiQ.mjs +141 -0
  37. package/dist/tests/digest.test.cjs +102 -0
  38. package/dist/tests/digest.test.d.cts +1 -0
  39. package/dist/tests/digest.test.d.mts +1 -0
  40. package/dist/tests/digest.test.mjs +103 -0
  41. package/dist/tests/docloader-C3YFl6y4.cjs +396 -0
  42. package/dist/tests/docloader-CvmS4sVq.mjs +373 -0
  43. package/dist/tests/docloader.test.cjs +692 -39
  44. package/dist/tests/docloader.test.mjs +686 -34
  45. package/dist/tests/internal/multicodec.test.cjs +2 -3
  46. package/dist/tests/internal/multicodec.test.mjs +2 -2
  47. package/dist/tests/internal/portable-dereference.test.cjs +172 -0
  48. package/dist/tests/internal/portable-dereference.test.d.cts +1 -0
  49. package/dist/tests/internal/portable-dereference.test.d.mts +1 -0
  50. package/dist/tests/internal/portable-dereference.test.mjs +173 -0
  51. package/dist/tests/jsonld-cache-BPQmOZWD.mjs +342 -0
  52. package/dist/tests/jsonld-cache-C07AyNOY.cjs +397 -0
  53. package/dist/tests/jsonld-cache.test.cjs +101 -298
  54. package/dist/tests/jsonld-cache.test.mjs +94 -289
  55. package/dist/tests/{key-_wXwomh_.cjs → key-C-AYkdJJ.cjs} +11 -4
  56. package/dist/tests/{key-CDGDH_vC.mjs → key-C2Db_TAJ.mjs} +11 -3
  57. package/dist/tests/key.test.cjs +6 -5
  58. package/dist/tests/key.test.mjs +6 -4
  59. package/dist/tests/langstr.test.cjs +4 -4
  60. package/dist/tests/langstr.test.mjs +2 -2
  61. package/dist/tests/link.test.cjs +2 -3
  62. package/dist/tests/link.test.mjs +2 -2
  63. package/dist/tests/multibase/multibase.test.cjs +8 -9
  64. package/dist/tests/multibase/multibase.test.mjs +6 -6
  65. package/dist/tests/{multibase-Bz_UUDtL.cjs → multibase-B5Mea7Ip.cjs} +19 -2
  66. package/dist/tests/{multibase-B4bvakyA.mjs → multibase-BPnF_L4e.mjs} +12 -1
  67. package/dist/tests/portable-dereference-BNXtgg5U.cjs +278 -0
  68. package/dist/tests/portable-dereference-DRE5bz-l.mjs +249 -0
  69. package/dist/tests/portable-media-BLKllU56.cjs +153 -0
  70. package/dist/tests/portable-media-DRSGBORB.mjs +148 -0
  71. package/dist/tests/portable-media.test.cjs +222 -0
  72. package/dist/tests/portable-media.test.d.cts +1 -0
  73. package/dist/tests/portable-media.test.d.mts +1 -0
  74. package/dist/tests/portable-media.test.mjs +223 -0
  75. package/dist/tests/portable-workers.test.cjs +36 -0
  76. package/dist/tests/portable-workers.test.d.cts +2 -0
  77. package/dist/tests/portable-workers.test.d.mts +2 -0
  78. package/dist/tests/portable-workers.test.mjs +35 -0
  79. package/dist/tests/{request-cCPgOxYG.cjs → request-8vPWtV1-.cjs} +10 -4
  80. package/dist/tests/{request-BOS-hNaf.mjs → request-DRaOaTqD.mjs} +8 -2
  81. package/dist/tests/request.test.cjs +7 -4
  82. package/dist/tests/request.test.mjs +5 -2
  83. package/dist/tests/signed-representation.test.cjs +588 -0
  84. package/dist/tests/signed-representation.test.d.cts +1 -0
  85. package/dist/tests/signed-representation.test.d.mts +1 -0
  86. package/dist/tests/signed-representation.test.mjs +589 -0
  87. package/dist/tests/temporal.test.cjs +1 -2
  88. package/dist/tests/temporal.test.mjs +1 -1
  89. package/dist/tests/url-BNakuZ8k.cjs +998 -0
  90. package/dist/tests/url-DMxmp7ZG.mjs +859 -0
  91. package/dist/tests/url.test.cjs +503 -3
  92. package/dist/tests/url.test.mjs +503 -2
  93. package/dist/url-DrGTR8yv.cjs +993 -0
  94. package/dist/url-Dzyp-NsC.js +860 -0
  95. package/package.json +27 -4
  96. package/scripts/test-bun.mjs +17 -0
  97. package/src/body.test.ts +125 -0
  98. package/src/body.ts +152 -0
  99. package/src/contexts/cid-v1.json +114 -0
  100. package/src/contexts/fep-22cd.json +21 -0
  101. package/src/contexts/fep-ef61.json +10 -0
  102. package/src/contexts/miscellany.json +17 -0
  103. package/src/contexts/security-data-integrity-v1.json +0 -4
  104. package/src/contexts.ts +40 -0
  105. package/src/digest.test.ts +220 -0
  106. package/src/digest.ts +229 -0
  107. package/src/docloader.test.ts +918 -27
  108. package/src/docloader.ts +322 -105
  109. package/src/internal/jsonld-cache.ts +97 -1
  110. package/src/internal/portable-dereference.test.ts +254 -0
  111. package/src/internal/portable-dereference.ts +1000 -0
  112. package/src/internal/signed-representation.ts +565 -0
  113. package/src/jsonld-cache.test.ts +89 -0
  114. package/src/key.test.ts +9 -0
  115. package/src/key.ts +11 -1
  116. package/src/mod.ts +30 -0
  117. package/src/multibase/multibase.test.ts +5 -5
  118. package/src/portable-media.test.ts +293 -0
  119. package/src/portable-media.ts +264 -0
  120. package/src/portable-workers.test.ts +81 -0
  121. package/src/portable.ts +180 -0
  122. package/src/preprocessor.ts +7 -0
  123. package/src/request.test.ts +10 -1
  124. package/src/request.ts +9 -1
  125. package/src/signed-representation.test.ts +338 -0
  126. package/src/url.test.ts +844 -1
  127. package/src/url.ts +717 -17
  128. package/tsdown.config.ts +2 -0
  129. package/dist/tests/url-BvjYQdxL.cjs +0 -456
  130. package/dist/tests/url-a2D8NAgh.mjs +0 -378
  131. package/dist/url-Ck3dGEwH.cjs +0 -457
  132. package/dist/url-m1YxGNZ0.js +0 -379
  133. /package/dist/{chunk-M78iaK0I.cjs → rolldown-runtime-B7lfambq.cjs} +0 -0
  134. /package/dist/tests/{langstr-CbAxaeEZ.cjs → langstr-C4Fl80ae.cjs} +0 -0
  135. /package/dist/tests/{langstr-Di5AvKpB.mjs → langstr-CQ26J_L7.mjs} +0 -0
  136. /package/dist/tests/{link-NUUWCdnK.mjs → link-Cevmc87v.mjs} +0 -0
  137. /package/dist/tests/{link-FguCydMA.cjs → link-DlKm8bEr.cjs} +0 -0
  138. /package/dist/tests/{multicodec-CxGVGa91.cjs → multicodec-CLRPeW4N.cjs} +0 -0
  139. /package/dist/tests/{multicodec-CyFp54fI.mjs → multicodec-CRIj_05H.mjs} +0 -0
  140. /package/dist/tests/{chunk-C2EiDwsr.cjs → rolldown-runtime-emK7D4bc.cjs} +0 -0
@@ -1,11 +1,17 @@
1
- const require_chunk = require("./chunk-C2EiDwsr.cjs");
2
- const require_docloader = require("./docloader-BFedvemb.cjs");
3
- const require_request = require("./request-cCPgOxYG.cjs");
4
- const require_url = require("./url-BvjYQdxL.cjs");
1
+ const require_rolldown_runtime = require("./rolldown-runtime-emK7D4bc.cjs");
2
+ const require_request = require("./request-8vPWtV1-.cjs");
3
+ const require_contexts = require("./contexts-DizzBjz4.cjs");
4
+ const require_url = require("./url-BNakuZ8k.cjs");
5
+ const require_docloader = require("./docloader-C3YFl6y4.cjs");
6
+ let node_zlib = require("node:zlib");
7
+ let _logtape_logtape = require("@logtape/logtape");
5
8
  let node_assert = require("node:assert");
6
- let node_test = require("node:test");
9
+ let node_dns_promises = require("node:dns/promises");
10
+ node_dns_promises = require_rolldown_runtime.__toESM(node_dns_promises, 1);
11
+ let _fedify_fixture = require("@fedify/fixture");
12
+ let node_http = require("node:http");
7
13
  //#region ../../node_modules/.pnpm/glob-to-regexp@0.4.1/node_modules/glob-to-regexp/index.js
8
- var require_glob_to_regexp = /* @__PURE__ */ require_chunk.__commonJSMin(((exports, module) => {
14
+ var require_glob_to_regexp = /* @__PURE__ */ require_rolldown_runtime.__commonJSMin(((exports, module) => {
9
15
  module.exports = function(glob, opts) {
10
16
  if (typeof glob !== "string") throw new TypeError("Expected a string");
11
17
  var str = String(glob);
@@ -79,7 +85,7 @@ var require_glob_to_regexp = /* @__PURE__ */ require_chunk.__commonJSMin(((expor
79
85
  }));
80
86
  //#endregion
81
87
  //#region ../../node_modules/.pnpm/regexparam@3.0.0/node_modules/regexparam/dist/index.mjs
82
- var import_glob_to_regexp = /* @__PURE__ */ require_chunk.__toESM(require_glob_to_regexp(), 1);
88
+ var import_glob_to_regexp = /* @__PURE__ */ require_rolldown_runtime.__toESM(require_glob_to_regexp(), 1);
83
89
  /**
84
90
  * @param {string|RegExp} input The route pattern
85
91
  * @param {boolean} [loose] Allow open-ended matching. Ignored with `RegExp` input.
@@ -111,27 +117,27 @@ function parse(input, loose) {
111
117
  }
112
118
  //#endregion
113
119
  //#region ../../node_modules/.pnpm/fetch-mock@12.6.0/node_modules/fetch-mock/dist/esm/TypeDescriptor.js
114
- const valueTypes = new Set([
120
+ const valueTypes = /* @__PURE__ */ new Set([
115
121
  "boolean",
116
122
  "number",
117
123
  "null",
118
124
  "string",
119
125
  "undefined"
120
126
  ]);
121
- const referenceTypes = new Set([
127
+ const referenceTypes = /* @__PURE__ */ new Set([
122
128
  "array",
123
129
  "function",
124
130
  "object",
125
131
  "symbol"
126
132
  ]);
127
- const detectableTypes = new Set([
133
+ const detectableTypes = /* @__PURE__ */ new Set([
128
134
  "boolean",
129
135
  "function",
130
136
  "number",
131
137
  "string",
132
138
  "symbol"
133
139
  ]);
134
- const typeConstructors = new Set([
140
+ const typeConstructors = /* @__PURE__ */ new Set([
135
141
  Boolean,
136
142
  Number,
137
143
  String
@@ -202,7 +208,7 @@ var TypeDescriptor = class TypeDescriptor {
202
208
  };
203
209
  //#endregion
204
210
  //#region ../../node_modules/.pnpm/fetch-mock@12.6.0/node_modules/fetch-mock/dist/esm/IsSubsetOf.js
205
- const allowedTypes = new Set([
211
+ const allowedTypes = /* @__PURE__ */ new Set([
206
212
  "array",
207
213
  "object",
208
214
  "function",
@@ -694,7 +700,14 @@ var __classPrivateFieldGet = function(receiver, state, kind, f) {
694
700
  if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
695
701
  return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
696
702
  };
697
- var _Route_instances, _a, _Route_responseSubscriptions, _Route_validate, _Route_sanitize, _Route_generateMatcher, _Route_limit, _Route_delayResponse;
703
+ var _Route_instances;
704
+ var _a;
705
+ var _Route_responseSubscriptions;
706
+ var _Route_validate;
707
+ var _Route_sanitize;
708
+ var _Route_generateMatcher;
709
+ var _Route_limit;
710
+ var _Route_delayResponse;
698
711
  var RouteConfigWrapper = class {
699
712
  constructor(config) {
700
713
  Object.assign(this, config);
@@ -1206,7 +1219,53 @@ var esm_default = new class FetchMock {
1206
1219
  }({ ...defaultFetchMockConfig });
1207
1220
  //#endregion
1208
1221
  //#region src/docloader.test.ts
1209
- (0, node_test.test)("new FetchError()", () => {
1222
+ (0, _fedify_fixture.test)("getRemoteDocument() forwards options to alternate documents", async () => {
1223
+ const records = [];
1224
+ await (0, _logtape_logtape.reset)();
1225
+ await (0, _logtape_logtape.configure)({
1226
+ sinks: { capture: (record) => records.push(record) },
1227
+ loggers: [{
1228
+ category: ["fedify"],
1229
+ lowestLevel: "debug",
1230
+ sinks: ["capture"]
1231
+ }]
1232
+ });
1233
+ try {
1234
+ for (const linkHeader of [true, false]) for (const suppressError of [
1235
+ true,
1236
+ false,
1237
+ void 0
1238
+ ]) {
1239
+ records.length = 0;
1240
+ const url = "https://example.com/alternate-source";
1241
+ const alternateUrl = "https://example.com/missing-alternate";
1242
+ const options = {
1243
+ suppressError,
1244
+ signal: new AbortController().signal
1245
+ };
1246
+ const response = new Response(`<link rel="alternate" type="application/activity+json" href="${alternateUrl}">`, { headers: {
1247
+ "Content-Type": "text/html",
1248
+ ...linkHeader ? { Link: `<${alternateUrl}>; rel="alternate"; type="application/activity+json"` } : {}
1249
+ } });
1250
+ let alternateRequests = 0;
1251
+ await (0, node_assert.rejects)(() => require_docloader.getRemoteDocument(url, response, async (requestedUrl, forwarded) => {
1252
+ alternateRequests++;
1253
+ (0, node_assert.deepStrictEqual)(requestedUrl, alternateUrl);
1254
+ (0, node_assert.deepStrictEqual)(forwarded, options);
1255
+ return await require_docloader.getRemoteDocument(requestedUrl, new Response(null, { status: 404 }), () => {
1256
+ throw new Error("Unexpected additional alternate");
1257
+ }, forwarded);
1258
+ }, options), require_request.FetchError);
1259
+ (0, node_assert.deepStrictEqual)(alternateRequests, 1);
1260
+ const failures = records.filter((record) => record.rawMessage === "Failed to fetch document: {status} {url} {headers}");
1261
+ (0, node_assert.deepStrictEqual)(failures.length, 1);
1262
+ (0, node_assert.deepStrictEqual)(failures[0].level, suppressError ? "warning" : "error");
1263
+ }
1264
+ } finally {
1265
+ await (0, _logtape_logtape.reset)();
1266
+ }
1267
+ });
1268
+ (0, _fedify_fixture.test)("new FetchError()", () => {
1210
1269
  const e = new require_request.FetchError("https://example.com/", "An error message.");
1211
1270
  (0, node_assert.deepStrictEqual)(e.name, "FetchError");
1212
1271
  (0, node_assert.deepStrictEqual)(e.url, new URL("https://example.com/"));
@@ -1219,7 +1278,7 @@ var esm_default = new class FetchMock {
1219
1278
  (0, node_assert.ok)(e2.response != null);
1220
1279
  (0, node_assert.deepStrictEqual)(e2.response.status, 410);
1221
1280
  });
1222
- (0, node_test.test)("getDocumentLoader()", async (t) => {
1281
+ (0, _fedify_fixture.test)("getDocumentLoader()", async (t) => {
1223
1282
  const fetchDocumentLoader = require_docloader.getDocumentLoader();
1224
1283
  esm_default.spyGlobal();
1225
1284
  esm_default.get("https://example.com/object", { body: {
@@ -1228,7 +1287,7 @@ var esm_default = new class FetchMock {
1228
1287
  name: "Fetched object",
1229
1288
  type: "Object"
1230
1289
  } });
1231
- await t.test("ok", async () => {
1290
+ await t.step("ok", async () => {
1232
1291
  (0, node_assert.deepStrictEqual)(await fetchDocumentLoader("https://example.com/object"), {
1233
1292
  contextUrl: null,
1234
1293
  documentUrl: "https://example.com/object",
@@ -1271,7 +1330,7 @@ var esm_default = new class FetchMock {
1271
1330
  Link: "<https://example.com/object>; rel=\"alternate\"; type=\"application/ld+json; profile=\"https://www.w3.org/ns/activitystreams\"\""
1272
1331
  }
1273
1332
  });
1274
- await t.test("Link header", async () => {
1333
+ await t.step("Link header", async () => {
1275
1334
  (0, node_assert.deepStrictEqual)(await fetchDocumentLoader("https://example.com/link-ctx"), {
1276
1335
  contextUrl: "https://www.w3.org/ns/activitystreams",
1277
1336
  documentUrl: "https://example.com/link-ctx",
@@ -1292,7 +1351,7 @@ var esm_default = new class FetchMock {
1292
1351
  }
1293
1352
  });
1294
1353
  });
1295
- await t.test("Link header relative url", async () => {
1354
+ await t.step("Link header relative url", async () => {
1296
1355
  (0, node_assert.deepStrictEqual)(await fetchDocumentLoader("https://example.com/link-ctx"), {
1297
1356
  contextUrl: "https://www.w3.org/ns/activitystreams",
1298
1357
  documentUrl: "https://example.com/link-ctx",
@@ -1313,7 +1372,7 @@ var esm_default = new class FetchMock {
1313
1372
  }
1314
1373
  });
1315
1374
  });
1316
- await t.test("wrong Link header syntax", async () => {
1375
+ await t.step("wrong Link header syntax", async () => {
1317
1376
  (0, node_assert.deepStrictEqual)(await fetchDocumentLoader("https://example.com/obj-w-wrong-link"), {
1318
1377
  contextUrl: null,
1319
1378
  documentUrl: "https://example.com/obj-w-wrong-link",
@@ -1337,7 +1396,7 @@ var esm_default = new class FetchMock {
1337
1396
  </html>`,
1338
1397
  headers: { "Content-Type": "text/html; charset=utf-8" }
1339
1398
  });
1340
- await t.test("HTML <link>", async () => {
1399
+ await t.step("HTML <link>", async () => {
1341
1400
  (0, node_assert.deepStrictEqual)(await fetchDocumentLoader("https://example.com/html-link"), {
1342
1401
  contextUrl: null,
1343
1402
  documentUrl: "https://example.com/object",
@@ -1361,7 +1420,7 @@ var esm_default = new class FetchMock {
1361
1420
  </html>`,
1362
1421
  headers: { "Content-Type": "application/xhtml+xml; charset=utf-8" }
1363
1422
  });
1364
- await t.test("XHTML <link>", async () => {
1423
+ await t.step("XHTML <link>", async () => {
1365
1424
  (0, node_assert.deepStrictEqual)(await fetchDocumentLoader("https://example.com/xhtml-link"), {
1366
1425
  contextUrl: null,
1367
1426
  documentUrl: "https://example.com/object",
@@ -1387,7 +1446,7 @@ var esm_default = new class FetchMock {
1387
1446
  </html>`,
1388
1447
  headers: { "Content-Type": "text/html; charset=utf-8" }
1389
1448
  });
1390
- await t.test("HTML <a>", async () => {
1449
+ await t.step("HTML <a>", async () => {
1391
1450
  (0, node_assert.deepStrictEqual)(await fetchDocumentLoader("https://example.com/html-a"), {
1392
1451
  contextUrl: null,
1393
1452
  documentUrl: "https://example.com/object",
@@ -1409,7 +1468,7 @@ var esm_default = new class FetchMock {
1409
1468
  </html>`,
1410
1469
  headers: { "Content-Type": "text/html; charset=utf-8" }
1411
1470
  });
1412
- await t.test("HTML without ActivityPub alternate link", async () => {
1471
+ await t.step("HTML without ActivityPub alternate link", async () => {
1413
1472
  await (0, node_assert.rejects)(() => fetchDocumentLoader("https://example.com/html-no-alternate"), (error) => {
1414
1473
  (0, node_assert.ok)(error instanceof require_request.FetchError);
1415
1474
  (0, node_assert.ok)(error.message.includes("HTML document has no ActivityPub alternate link"));
@@ -1429,7 +1488,7 @@ var esm_default = new class FetchMock {
1429
1488
  },
1430
1489
  headers: { "Content-Type": "text/html; charset=utf-8" }
1431
1490
  });
1432
- await t.test("wrong Content-Type with JSON body", async () => {
1491
+ await t.step("wrong Content-Type with JSON body", async () => {
1433
1492
  (0, node_assert.deepStrictEqual)(await fetchDocumentLoader("https://example.com/wrong-content-type"), {
1434
1493
  contextUrl: null,
1435
1494
  documentUrl: "https://example.com/wrong-content-type",
@@ -1448,7 +1507,7 @@ var esm_default = new class FetchMock {
1448
1507
  "Content-Type": "text/html; charset=utf-8"
1449
1508
  }
1450
1509
  });
1451
- await t.test("HTML Content-Length over limit", async () => {
1510
+ await t.step("HTML Content-Length over limit", async () => {
1452
1511
  await (0, node_assert.rejects)(() => fetchDocumentLoader("https://example.com/large-html"), (error) => {
1453
1512
  (0, node_assert.ok)(error instanceof require_request.FetchError);
1454
1513
  (0, node_assert.ok)(error.message.includes("HTML document is too large to scan for an ActivityPub alternate link"));
@@ -1458,32 +1517,88 @@ var esm_default = new class FetchMock {
1458
1517
  return true;
1459
1518
  });
1460
1519
  });
1461
- await t.test("HTML Content-Length over limit cancels body", async () => {
1520
+ await t.step("HTML Content-Length over limit cancels body", async () => {
1462
1521
  let canceled = false;
1463
1522
  const response = new Response("<!DOCTYPE html>", { headers: {
1464
1523
  "Content-Length": String(1048577),
1465
1524
  "Content-Type": "text/html; charset=utf-8"
1466
1525
  } });
1467
- Object.defineProperty(response, "body", { value: { cancel: () => {
1526
+ Object.defineProperty(response, "body", { value: new ReadableStream({ cancel() {
1468
1527
  canceled = true;
1469
- } } });
1528
+ } }) });
1470
1529
  await (0, node_assert.rejects)(() => require_docloader.getRemoteDocument("https://example.com/large-html-cancel", response, () => {
1471
1530
  throw new Error("unexpected alternate fetch");
1472
1531
  }), require_request.FetchError);
1473
1532
  (0, node_assert.deepStrictEqual)(canceled, true);
1474
1533
  });
1475
1534
  esm_default.get("https://example.com/404", { status: 404 });
1476
- await t.test("not ok", async () => {
1535
+ await t.step("not ok", async () => {
1477
1536
  await (0, node_assert.rejects)(() => fetchDocumentLoader("https://example.com/404"), require_request.FetchError, "HTTP 404: https://example.com/404");
1478
1537
  });
1479
- await t.test("preloaded contexts", async () => {
1480
- for (const [url, document] of Object.entries(require_docloader.preloadedContexts)) (0, node_assert.deepStrictEqual)(await fetchDocumentLoader(url), {
1538
+ await t.step("preloaded contexts", async () => {
1539
+ for (const [url, document] of Object.entries(require_contexts.preloadedContexts)) (0, node_assert.deepStrictEqual)(await fetchDocumentLoader(url), {
1481
1540
  contextUrl: null,
1482
1541
  documentUrl: url,
1483
1542
  document
1484
1543
  });
1485
1544
  });
1486
- await t.test("deny non-HTTP/HTTPS", async () => {
1545
+ esm_default.get("https://www.w3.org/ns/cid/v1", { status: 503 });
1546
+ await t.step("preloaded CID v1 context", async () => {
1547
+ const url = "https://www.w3.org/ns/cid/v1";
1548
+ (0, node_assert.deepStrictEqual)(await fetchDocumentLoader(url), {
1549
+ contextUrl: null,
1550
+ documentUrl: url,
1551
+ document: require_contexts.cid_v1_default
1552
+ });
1553
+ });
1554
+ await t.step("preloaded FEP-ef61 context", async () => {
1555
+ const url = "https://w3id.org/fep/ef61";
1556
+ (0, node_assert.ok)(url in require_contexts.preloadedContexts);
1557
+ (0, node_assert.deepStrictEqual)(await fetchDocumentLoader(url), {
1558
+ contextUrl: null,
1559
+ documentUrl: url,
1560
+ document: { "@context": {
1561
+ gateways: {
1562
+ "@id": "https://w3id.org/fep/ef61/gateways",
1563
+ "@type": "@id",
1564
+ "@container": "@list"
1565
+ },
1566
+ digestMultibase: "https://www.w3.org/ns/credentials/v2#digestMultibase"
1567
+ } }
1568
+ });
1569
+ });
1570
+ esm_default.get("https://w3id.org/fep/7aa9", { status: 502 });
1571
+ await t.step("preloaded FEP-7aa9 context", async () => {
1572
+ const url = "https://w3id.org/fep/7aa9";
1573
+ (0, node_assert.deepStrictEqual)(await fetchDocumentLoader(url), {
1574
+ contextUrl: null,
1575
+ documentUrl: url,
1576
+ document: { "@context": {
1577
+ "FeaturedCollection": "https://w3id.org/fep/7aa9#FeaturedCollection",
1578
+ "FeaturedItem": "https://w3id.org/fep/7aa9#FeaturedItem",
1579
+ "FeatureRequest": "https://w3id.org/fep/7aa9#FeatureRequest",
1580
+ "FeatureAuthorization": "https://w3id.org/fep/7aa9#FeatureAuthorization",
1581
+ "topic": {
1582
+ "@id": "https://w3id.org/fep/7aa9#topic",
1583
+ "@type": "@id"
1584
+ },
1585
+ "featuredObject": {
1586
+ "@id": "https://w3id.org/fep/7aa9#featuredObject",
1587
+ "@type": "@id"
1588
+ },
1589
+ "canFeature": {
1590
+ "@id": "https://w3id.org/fep/7aa9#canFeature",
1591
+ "@type": "@id"
1592
+ },
1593
+ "featureAuthorization": {
1594
+ "@id": "https://w3id.org/fep/7aa9#featureAuthorization",
1595
+ "@type": "@id"
1596
+ }
1597
+ } }
1598
+ });
1599
+ (0, node_assert.deepStrictEqual)(esm_default.callHistory.calls(url).length, 0);
1600
+ });
1601
+ await t.step("deny non-HTTP/HTTPS", async () => {
1487
1602
  await (0, node_assert.rejects)(() => fetchDocumentLoader("ftp://localhost"), require_url.UrlError);
1488
1603
  });
1489
1604
  esm_default.get("https://example.com/localhost-redirect", {
@@ -1508,13 +1623,13 @@ var esm_default = new class FetchMock {
1508
1623
  name: "Fetched object",
1509
1624
  type: "Object"
1510
1625
  } });
1511
- await t.test("allowPrivateAddress: false", async () => {
1626
+ await t.step("allowPrivateAddress: false", async () => {
1512
1627
  await (0, node_assert.rejects)(() => fetchDocumentLoader("https://localhost/object"), require_url.UrlError);
1513
1628
  await (0, node_assert.rejects)(() => fetchDocumentLoader("https://example.com/localhost-redirect"), require_url.UrlError);
1514
1629
  await (0, node_assert.rejects)(() => fetchDocumentLoader("https://example.com/localhost-link"), require_url.UrlError);
1515
1630
  });
1516
1631
  const fetchDocumentLoader2 = require_docloader.getDocumentLoader({ allowPrivateAddress: true });
1517
- await t.test("allowPrivateAddress: true", async () => {
1632
+ await t.step("allowPrivateAddress: true", async () => {
1518
1633
  const expected = {
1519
1634
  contextUrl: null,
1520
1635
  documentUrl: "https://localhost/object",
@@ -1537,12 +1652,12 @@ var esm_default = new class FetchMock {
1537
1652
  headers: { Location: `https://example.com/too-many-redirects/${Number(cl.url.split("/").at(-1)) + 1}` }
1538
1653
  };
1539
1654
  });
1540
- await t.test("too many redirects", async () => {
1655
+ await t.step("too many redirects", async () => {
1541
1656
  redirectAttempts = 0;
1542
1657
  await (0, node_assert.rejects)(() => fetchDocumentLoader("https://example.com/too-many-redirects/0"), require_request.FetchError, "Too many redirections");
1543
1658
  (0, node_assert.deepStrictEqual)(redirectAttempts, 21);
1544
1659
  });
1545
- await t.test("custom max redirection", async () => {
1660
+ await t.step("custom max redirection", async () => {
1546
1661
  redirectAttempts = 0;
1547
1662
  const loader = require_docloader.getDocumentLoader({ maxRedirection: 1 });
1548
1663
  await (0, node_assert.rejects)(() => loader("https://example.com/too-many-redirects/0"), require_request.FetchError, "Too many redirections");
@@ -1563,7 +1678,7 @@ var esm_default = new class FetchMock {
1563
1678
  headers: { Location: "https://example.com/redirect-loop-a" }
1564
1679
  };
1565
1680
  });
1566
- await t.test("redirect loop", async () => {
1681
+ await t.step("redirect loop", async () => {
1567
1682
  loopAttempts = 0;
1568
1683
  await (0, node_assert.rejects)(() => fetchDocumentLoader("https://example.com/redirect-loop-a"), require_request.FetchError, "Redirect loop detected");
1569
1684
  (0, node_assert.deepStrictEqual)(loopAttempts, 2);
@@ -1576,7 +1691,7 @@ var esm_default = new class FetchMock {
1576
1691
  headers: { Location: "/redirect-loop-relative" }
1577
1692
  };
1578
1693
  });
1579
- await t.test("redirect loop with relative location", async () => {
1694
+ await t.step("redirect loop with relative location", async () => {
1580
1695
  relativeLoopAttempts = 0;
1581
1696
  await (0, node_assert.rejects)(() => fetchDocumentLoader("https://example.com/redirect-loop-relative"), require_request.FetchError, "Redirect loop detected");
1582
1697
  (0, node_assert.deepStrictEqual)(relativeLoopAttempts, 1);
@@ -1586,7 +1701,7 @@ var esm_default = new class FetchMock {
1586
1701
  body: maliciousPayload,
1587
1702
  headers: { "Content-Type": "text/html; charset=utf-8" }
1588
1703
  });
1589
- await t.test("ReDoS resistance (CVE-2025-68475)", async () => {
1704
+ await t.step("ReDoS resistance (CVE-2025-68475)", async () => {
1590
1705
  const start = performance.now();
1591
1706
  await (0, node_assert.rejects)(() => fetchDocumentLoader("https://example.com/redos"), require_request.FetchError);
1592
1707
  const elapsed = performance.now() - start;
@@ -1594,4 +1709,542 @@ var esm_default = new class FetchMock {
1594
1709
  });
1595
1710
  esm_default.hardReset();
1596
1711
  });
1712
+ (0, _fedify_fixture.test)("getDocumentLoader() bounds JSON, HTML, and alternate documents", async () => {
1713
+ const url = "https://example.com/bounded";
1714
+ esm_default.mockGlobal();
1715
+ let oversized = true;
1716
+ try {
1717
+ esm_default.get(url, () => new Response("{\"name\":\"hello\"}", { headers: {
1718
+ "Content-Type": "application/activity+json",
1719
+ ...oversized ? { "Content-Length": String(16777217) } : {}
1720
+ } }));
1721
+ const loader = require_docloader.getDocumentLoader({ allowPrivateAddress: true });
1722
+ await (0, node_assert.rejects)(loader(url), require_request.FetchError);
1723
+ oversized = false;
1724
+ (0, node_assert.deepStrictEqual)((await loader(url)).document, { name: "hello" });
1725
+ oversized = true;
1726
+ esm_default.get(`${url}/html`, () => new Response(" ".repeat(1048577), { headers: { "Content-Type": "text/html" } }));
1727
+ await (0, node_assert.rejects)(require_docloader.getDocumentLoader({ allowPrivateAddress: true })(`${url}/html`), require_request.FetchError);
1728
+ esm_default.get(`${url}/alternate`, () => new Response("", { headers: {
1729
+ "Content-Type": "text/html",
1730
+ Link: `<${url}>; rel="alternate"; type="application/activity+json"`
1731
+ } }));
1732
+ await (0, node_assert.rejects)(loader(`${url}/alternate`), require_request.FetchError);
1733
+ esm_default.get(`${url}/redirect`, {
1734
+ status: 302,
1735
+ headers: { Location: url }
1736
+ });
1737
+ await (0, node_assert.rejects)(loader(`${url}/redirect`), require_request.FetchError);
1738
+ } finally {
1739
+ esm_default.hardReset();
1740
+ }
1741
+ });
1742
+ (0, _fedify_fixture.test)("getDocumentLoader() rejects oversized gzip responses from fetch", async () => {
1743
+ const document = { name: "a".repeat(16 * 1024 * 1024) };
1744
+ const compressed = (0, node_zlib.gzipSync)(JSON.stringify(document));
1745
+ (0, node_assert.ok)(compressed.byteLength < 64 * 1024);
1746
+ const server = (0, node_http.createServer)((request, response) => {
1747
+ const body = request.url === "/small" ? (0, node_zlib.gzipSync)("{\"name\":\"hello\"}") : compressed;
1748
+ response.writeHead(200, {
1749
+ "Content-Type": "application/activity+json",
1750
+ "Content-Encoding": "gzip",
1751
+ "Content-Length": body.byteLength
1752
+ });
1753
+ response.end(body);
1754
+ });
1755
+ await new Promise((resolve, reject) => {
1756
+ server.once("error", reject);
1757
+ server.listen(0, "127.0.0.1", resolve);
1758
+ });
1759
+ try {
1760
+ const address = server.address();
1761
+ (0, node_assert.ok)(address != null && typeof address !== "string");
1762
+ const url = `http://127.0.0.1:${address.port}/document`;
1763
+ await (0, node_assert.rejects)(require_docloader.getDocumentLoader({ allowPrivateAddress: true })(url), require_request.FetchError);
1764
+ (0, node_assert.deepStrictEqual)((await require_docloader.getDocumentLoader({ allowPrivateAddress: true })(new URL("/small", url).href)).document, { name: "hello" });
1765
+ } finally {
1766
+ await new Promise((resolve, reject) => {
1767
+ server.close((error) => error == null ? resolve() : reject(error));
1768
+ server.closeAllConnections();
1769
+ });
1770
+ }
1771
+ });
1772
+ (0, _fedify_fixture.test)("getRemoteDocument() bounds JSON by default", async () => {
1773
+ let canceled = false;
1774
+ let pulls = 0;
1775
+ await (0, node_assert.rejects)(require_docloader.getRemoteDocument("https://example.com/document", new Response(new ReadableStream({
1776
+ pull(controller) {
1777
+ pulls++;
1778
+ controller.enqueue(new Uint8Array(64 * 1024).fill(32));
1779
+ },
1780
+ cancel() {
1781
+ canceled = true;
1782
+ }
1783
+ }, { highWaterMark: 0 })), () => {
1784
+ throw new Error("Unexpected alternate document");
1785
+ }), require_request.FetchError);
1786
+ (0, node_assert.deepStrictEqual)(canceled, true);
1787
+ (0, node_assert.deepStrictEqual)(pulls, 257);
1788
+ });
1789
+ (0, _fedify_fixture.test)("getDocumentLoader() logs DNS failures as such", async (t) => {
1790
+ if ("Deno" in globalThis && (await Deno.permissions.query({ name: "net" })).state !== "granted") throw new Error("This DNS test requires Deno network permission (--allow-net).");
1791
+ const loader = require_docloader.getDocumentLoader();
1792
+ for (const result of [
1793
+ "throws",
1794
+ "empty",
1795
+ "private"
1796
+ ]) await t.step(result, async () => {
1797
+ const originalLookup = node_dns_promises.default.lookup;
1798
+ node_dns_promises.default.lookup = (() => result === "throws" ? Promise.reject(/* @__PURE__ */ new Error("Resolver unavailable")) : Promise.resolve(result === "empty" ? [] : [{
1799
+ address: "127.0.0.1",
1800
+ family: 4
1801
+ }]));
1802
+ const records = [];
1803
+ await (0, _logtape_logtape.configure)({
1804
+ sinks: { buffer: (record) => records.push(record) },
1805
+ loggers: [{
1806
+ category: "fedify",
1807
+ sinks: ["buffer"],
1808
+ lowestLevel: "debug"
1809
+ }, {
1810
+ category: ["logtape", "meta"],
1811
+ sinks: []
1812
+ }],
1813
+ reset: true
1814
+ });
1815
+ try {
1816
+ const url = "https://dns-failure.invalid/object";
1817
+ let error;
1818
+ await (0, node_assert.rejects)(() => loader(url), (e) => {
1819
+ error = e;
1820
+ return true;
1821
+ });
1822
+ (0, node_assert.ok)(error instanceof require_url.UrlError);
1823
+ (0, node_assert.deepStrictEqual)(error.reason, result === "private" ? "disallowed" : "dns");
1824
+ (0, node_assert.deepStrictEqual)(records.map((r) => [
1825
+ r.level,
1826
+ r.rawMessage,
1827
+ r.properties.url
1828
+ ]), [result === "private" ? [
1829
+ "error",
1830
+ "Disallowed private URL: {url}",
1831
+ url
1832
+ ] : [
1833
+ "debug",
1834
+ "DNS lookup failed for {url}",
1835
+ url
1836
+ ]]);
1837
+ (0, node_assert.ok)(records[0].properties.error === error);
1838
+ } finally {
1839
+ await (0, _logtape_logtape.reset)();
1840
+ node_dns_promises.default.lookup = originalLookup;
1841
+ }
1842
+ });
1843
+ });
1844
+ (0, _fedify_fixture.test)("getDocumentLoader() bounds alternate document chains", async (t) => {
1845
+ const base = "https://example.com/alternate-chain/";
1846
+ const loader = require_docloader.getDocumentLoader({ allowPrivateAddress: true });
1847
+ for (const mode of [
1848
+ "header",
1849
+ "html",
1850
+ "mixed"
1851
+ ]) for (const hops of [20, 21]) await t.step(`${mode}: ${hops} hops`, async () => {
1852
+ esm_default.mockGlobal();
1853
+ let requests = 0;
1854
+ esm_default.get(`begin:${base}`, ({ url }) => {
1855
+ requests++;
1856
+ const index = Number(new URL(url).pathname.split("/").at(-1));
1857
+ if (index === hops || requests > 30) return Response.json({ done: true });
1858
+ const next = `${base}${index + 1}`;
1859
+ if (mode === "mixed" && index % 2 === 1) return Response.redirect(next, 302);
1860
+ return alternate(next, mode === "html");
1861
+ });
1862
+ try {
1863
+ if (hops === 20) (0, node_assert.deepStrictEqual)((await loader(`${base}0`)).document, { done: true });
1864
+ else await (0, node_assert.rejects)(loader(`${base}0`), (error) => {
1865
+ (0, node_assert.ok)(error instanceof require_request.FetchError);
1866
+ (0, node_assert.ok)(error.message.includes("Too many redirections (21)"));
1867
+ return true;
1868
+ });
1869
+ (0, node_assert.deepStrictEqual)(requests, 21);
1870
+ } finally {
1871
+ esm_default.hardReset();
1872
+ }
1873
+ });
1874
+ for (const mode of [
1875
+ "header",
1876
+ "html",
1877
+ "alternate-redirect",
1878
+ "redirect-alternate",
1879
+ "redirect-intermediate"
1880
+ ]) await t.step(`${mode}: cycle`, async () => {
1881
+ esm_default.mockGlobal();
1882
+ let requests = 0;
1883
+ esm_default.get(`begin:${base}`, ({ url }) => {
1884
+ requests++;
1885
+ if (requests > 30) return Response.json({ stopped: true });
1886
+ const index = Number(new URL(url).pathname.split("/").at(-1));
1887
+ const next = `${base}${mode === "redirect-intermediate" ? index === 0 ? 1 : index === 1 ? 2 : 1 : 1 - index}`;
1888
+ if (mode === "alternate-redirect" && index === 1 || (mode === "redirect-alternate" || mode === "redirect-intermediate") && index === 0) return Response.redirect(next, 302);
1889
+ return alternate(next, mode === "html");
1890
+ });
1891
+ try {
1892
+ await (0, node_assert.rejects)(loader(`${base}0`), (error) => {
1893
+ (0, node_assert.ok)(error instanceof require_request.FetchError);
1894
+ (0, node_assert.ok)(error.message.includes("Redirect loop detected:"));
1895
+ return true;
1896
+ });
1897
+ (0, node_assert.deepStrictEqual)(requests, mode === "redirect-intermediate" ? 3 : 2);
1898
+ } finally {
1899
+ esm_default.hardReset();
1900
+ }
1901
+ });
1902
+ await t.step("alternate followed by 20 redirects shares the limit", async () => {
1903
+ esm_default.mockGlobal();
1904
+ let requests = 0;
1905
+ esm_default.get(`begin:${base}`, ({ url }) => {
1906
+ requests++;
1907
+ const index = Number(new URL(url).pathname.split("/").at(-1));
1908
+ if (index === 21) return Response.json({ done: true });
1909
+ return index === 0 ? alternate(`${base}1`, false) : Response.redirect(`${base}${index + 1}`, 302);
1910
+ });
1911
+ try {
1912
+ await (0, node_assert.rejects)(loader(`${base}0`), (error) => {
1913
+ (0, node_assert.ok)(error instanceof require_request.FetchError);
1914
+ (0, node_assert.ok)(error.message.includes("Too many redirections (21)"));
1915
+ return true;
1916
+ });
1917
+ (0, node_assert.deepStrictEqual)(requests, 21);
1918
+ } finally {
1919
+ esm_default.hardReset();
1920
+ }
1921
+ });
1922
+ await t.step("relative alternate after redirect and isolated calls", async () => {
1923
+ esm_default.mockGlobal();
1924
+ esm_default.get(`${base}start`, Response.redirect(`${base}html`, 302));
1925
+ esm_default.get(`${base}html`, alternate("./document", true));
1926
+ esm_default.get(`${base}document`, Response.json({ done: true }));
1927
+ try {
1928
+ for (let i = 0; i < 2; i++) {
1929
+ const results = await Promise.all([loader(`${base}start`), loader(`${base}start`)]);
1930
+ for (const result of results) {
1931
+ (0, node_assert.deepStrictEqual)(result.document, { done: true });
1932
+ (0, node_assert.deepStrictEqual)(result.documentUrl, `${base}document`);
1933
+ }
1934
+ }
1935
+ } finally {
1936
+ esm_default.hardReset();
1937
+ }
1938
+ });
1939
+ function alternate(next, html) {
1940
+ return html ? new Response(`<link rel="alternate" type="application/activity+json" href="${next}">`, { headers: { "Content-Type": "text/html" } }) : new Response("not JSON", { headers: {
1941
+ "Content-Type": "text/plain",
1942
+ Link: `<${next}>; rel="alternate"; type="application/activity+json"`
1943
+ } });
1944
+ }
1945
+ });
1946
+ (0, _fedify_fixture.test)("getDocumentLoader() preserves cancellation across alternates", async (t) => {
1947
+ const base = "https://example.com/alternate-abort/";
1948
+ const loader = require_docloader.getDocumentLoader({ allowPrivateAddress: true });
1949
+ for (const html of [false, true]) await t.step(html ? "HTML" : "Link header", async () => {
1950
+ esm_default.mockGlobal();
1951
+ const controller = new AbortController();
1952
+ let requests = 0;
1953
+ esm_default.get(`begin:${base}`, ({ url }) => {
1954
+ requests++;
1955
+ const index = Number(new URL(url).pathname.split("/").at(-1));
1956
+ if (index === 2) return Response.json({ done: true });
1957
+ if (index === 1) controller.abort();
1958
+ const next = `${base}${index + 1}`;
1959
+ return new Response(html ? `<link rel="alternate" type="application/activity+json" href="${next}">` : "not JSON", { headers: html ? { "Content-Type": "text/html" } : {
1960
+ "Content-Type": "text/plain",
1961
+ Link: `<${next}>; rel="alternate"; type="application/activity+json"`
1962
+ } });
1963
+ });
1964
+ try {
1965
+ await (0, node_assert.rejects)(loader(`${base}0`, { signal: controller.signal }), { name: "AbortError" });
1966
+ (0, node_assert.deepStrictEqual)(requests, 2);
1967
+ } finally {
1968
+ esm_default.hardReset();
1969
+ }
1970
+ });
1971
+ });
1972
+ (0, _fedify_fixture.test)("getDocumentLoader() rejects cancellation before fetching", async () => {
1973
+ esm_default.mockGlobal();
1974
+ let requests = 0;
1975
+ const url = "https://example.com/pre-aborted-alternate";
1976
+ esm_default.get(url, () => {
1977
+ requests++;
1978
+ return Response.json({ done: true });
1979
+ });
1980
+ try {
1981
+ const loader = require_docloader.getDocumentLoader({ allowPrivateAddress: true });
1982
+ const controller = new AbortController();
1983
+ controller.abort();
1984
+ await (0, node_assert.rejects)(loader(url, { signal: controller.signal }), { name: "AbortError" });
1985
+ (0, node_assert.deepStrictEqual)(requests, 0);
1986
+ } finally {
1987
+ esm_default.hardReset();
1988
+ }
1989
+ });
1990
+ async function withServer(handler, callback) {
1991
+ const server = (0, node_http.createServer)(handler);
1992
+ await new Promise((resolve, reject) => {
1993
+ server.once("error", reject);
1994
+ server.listen(0, "127.0.0.1", resolve);
1995
+ });
1996
+ try {
1997
+ const address = server.address();
1998
+ (0, node_assert.ok)(address != null && typeof address !== "string");
1999
+ return await callback(`http://127.0.0.1:${address.port}`);
2000
+ } finally {
2001
+ await new Promise((resolve, reject) => {
2002
+ server.close((error) => error == null ? resolve() : reject(error));
2003
+ server.closeAllConnections();
2004
+ });
2005
+ }
2006
+ }
2007
+ function isTimeoutFetchError(error, url) {
2008
+ (0, node_assert.ok)(error instanceof require_request.FetchError, String(error));
2009
+ (0, node_assert.deepStrictEqual)(error.url.href, url);
2010
+ (0, node_assert.deepStrictEqual)(error.response, void 0);
2011
+ (0, node_assert.ok)(error.cause instanceof DOMException);
2012
+ (0, node_assert.deepStrictEqual)(error.cause.name, "TimeoutError");
2013
+ return true;
2014
+ }
2015
+ (0, _fedify_fixture.test)("resolveDocumentLoaderTimeout() validates timeouts", () => {
2016
+ (0, node_assert.deepStrictEqual)(require_docloader.resolveDocumentLoaderTimeout(void 0), 1e4);
2017
+ (0, node_assert.deepStrictEqual)(require_docloader.resolveDocumentLoaderTimeout(null), null);
2018
+ (0, node_assert.deepStrictEqual)(require_docloader.resolveDocumentLoaderTimeout(1500), 1500);
2019
+ (0, node_assert.deepStrictEqual)(require_docloader.resolveDocumentLoaderTimeout(.5), 1);
2020
+ (0, node_assert.deepStrictEqual)(require_docloader.resolveDocumentLoaderTimeout(2147483647), 2147483647);
2021
+ for (const invalid of [
2022
+ 0,
2023
+ -1,
2024
+ NaN,
2025
+ Infinity,
2026
+ 2147483648
2027
+ ]) (0, node_assert.throws)(() => require_docloader.resolveDocumentLoaderTimeout(invalid), RangeError);
2028
+ (0, node_assert.throws)(() => require_docloader.getDocumentLoader({ timeout: 0 }), RangeError);
2029
+ });
2030
+ (0, _fedify_fixture.test)("getDocumentLoader() times out a request without a response", async () => {
2031
+ await withServer(() => {}, async (baseUrl) => {
2032
+ const url = `${baseUrl}/hang`;
2033
+ await (0, node_assert.rejects)(require_docloader.getDocumentLoader({
2034
+ allowPrivateAddress: true,
2035
+ timeout: 100
2036
+ })(url), (e) => isTimeoutFetchError(e, url));
2037
+ });
2038
+ });
2039
+ (0, _fedify_fixture.test)("getDocumentLoader() times out a stalled response body", async () => {
2040
+ for (const status of [200, 404]) await withServer((_, response) => {
2041
+ response.writeHead(status, { "Content-Type": "application/activity+json" });
2042
+ response.write("{\"id\":");
2043
+ }, async (baseUrl) => {
2044
+ const url = `${baseUrl}/stalled`;
2045
+ await (0, node_assert.rejects)(require_docloader.getDocumentLoader({
2046
+ allowPrivateAddress: true,
2047
+ timeout: 200
2048
+ })(url), (e) => isTimeoutFetchError(e, url));
2049
+ });
2050
+ });
2051
+ (0, _fedify_fixture.test)("getDocumentLoader() shares the timeout across redirects", async () => {
2052
+ let requests = 0;
2053
+ await withServer((request, response) => {
2054
+ requests++;
2055
+ const index = Number(request.url?.split("/").at(-1));
2056
+ setTimeout(() => {
2057
+ if (index >= 15) {
2058
+ response.writeHead(200, { "Content-Type": "application/activity+json" });
2059
+ response.end("{\"done\":true}");
2060
+ return;
2061
+ }
2062
+ response.writeHead(302, { Location: `/redirect/${index + 1}` });
2063
+ response.end();
2064
+ }, 50);
2065
+ }, async (baseUrl) => {
2066
+ const url = `${baseUrl}/redirect/0`;
2067
+ await (0, node_assert.rejects)(require_docloader.getDocumentLoader({
2068
+ allowPrivateAddress: true,
2069
+ timeout: 300
2070
+ })(url), (e) => isTimeoutFetchError(e, url));
2071
+ (0, node_assert.ok)(requests < 16, `requests: ${requests}`);
2072
+ (0, node_assert.deepStrictEqual)((await require_docloader.getDocumentLoader({
2073
+ allowPrivateAddress: true,
2074
+ timeout: null
2075
+ })(url)).document, { done: true });
2076
+ });
2077
+ });
2078
+ (0, _fedify_fixture.test)("getDocumentLoader() shares the timeout across alternates", async (t) => {
2079
+ for (const html of [false, true]) await t.step(html ? "HTML" : "Link header", async () => {
2080
+ await withServer((request, response) => {
2081
+ const index = Number(request.url?.split("/").at(-1));
2082
+ setTimeout(() => {
2083
+ if (index >= 15) {
2084
+ response.writeHead(200, { "Content-Type": "application/activity+json" });
2085
+ response.end("{\"done\":true}");
2086
+ return;
2087
+ }
2088
+ const next = `/alternate/${index + 1}`;
2089
+ response.writeHead(200, html ? { "Content-Type": "text/html" } : {
2090
+ "Content-Type": "text/plain",
2091
+ Link: `<${next}>; rel="alternate"; type="application/activity+json"`
2092
+ });
2093
+ response.end(html ? `<link rel="alternate" type="application/activity+json" href="${next}">` : "not JSON");
2094
+ }, 50);
2095
+ }, async (baseUrl) => {
2096
+ const url = `${baseUrl}/alternate/0`;
2097
+ await (0, node_assert.rejects)(require_docloader.getDocumentLoader({
2098
+ allowPrivateAddress: true,
2099
+ timeout: 300
2100
+ })(url), (e) => isTimeoutFetchError(e, url));
2101
+ });
2102
+ });
2103
+ });
2104
+ (0, _fedify_fixture.test)("getDocumentLoader() lets the caller's signal abort a request", async () => {
2105
+ await withServer(() => {}, async (baseUrl) => {
2106
+ const url = `${baseUrl}/hang`;
2107
+ for (const timeout of [
2108
+ void 0,
2109
+ 6e4,
2110
+ null
2111
+ ]) {
2112
+ const loader = require_docloader.getDocumentLoader({
2113
+ allowPrivateAddress: true,
2114
+ timeout
2115
+ });
2116
+ const controller = new AbortController();
2117
+ const reason = /* @__PURE__ */ new Error("Canceled by the caller");
2118
+ setTimeout(() => controller.abort(reason), 50);
2119
+ await (0, node_assert.rejects)(loader(url, { signal: controller.signal }), (e) => {
2120
+ (0, node_assert.deepStrictEqual)(e, reason);
2121
+ return true;
2122
+ });
2123
+ }
2124
+ const loader = require_docloader.getDocumentLoader({
2125
+ allowPrivateAddress: true,
2126
+ timeout: 50
2127
+ });
2128
+ const controller = new AbortController();
2129
+ const reason = /* @__PURE__ */ new Error("Canceled by the caller");
2130
+ controller.abort(reason);
2131
+ await (0, node_assert.rejects)(loader(url, { signal: controller.signal }), (e) => {
2132
+ (0, node_assert.deepStrictEqual)(e, reason);
2133
+ return true;
2134
+ });
2135
+ });
2136
+ });
2137
+ (0, _fedify_fixture.test)("getDocumentLoader() bounds DNS lookups that ignore the timeout", async () => {
2138
+ if ("Deno" in globalThis && (await Deno.permissions.query({ name: "net" })).state !== "granted") throw new Error("This DNS test requires Deno network permission (--allow-net).");
2139
+ esm_default.mockGlobal();
2140
+ let requests = 0;
2141
+ const url = "https://slow-dns.example/object";
2142
+ esm_default.get(url, () => {
2143
+ requests++;
2144
+ return Response.json({ id: url });
2145
+ });
2146
+ const originalLookup = node_dns_promises.default.lookup;
2147
+ let resolveLookup = () => {};
2148
+ const lookedUp = new Promise((resolve) => resolveLookup = resolve);
2149
+ node_dns_promises.default.lookup = (() => new Promise((resolve) => setTimeout(() => {
2150
+ resolve([{
2151
+ address: "93.184.215.14",
2152
+ family: 4
2153
+ }]);
2154
+ resolveLookup();
2155
+ }, 200)));
2156
+ try {
2157
+ await (0, node_assert.rejects)(require_docloader.getDocumentLoader({ timeout: 50 })(url), (e) => isTimeoutFetchError(e, url));
2158
+ await lookedUp;
2159
+ await new Promise((resolve) => setTimeout(resolve, 10));
2160
+ (0, node_assert.deepStrictEqual)(requests, 0);
2161
+ } finally {
2162
+ node_dns_promises.default.lookup = originalLookup;
2163
+ esm_default.hardReset();
2164
+ }
2165
+ });
2166
+ (0, _fedify_fixture.test)("withDocumentLoaderTimeout() cleans up after each call", async () => {
2167
+ const signals = [];
2168
+ const loader = require_docloader.withDocumentLoaderTimeout((url, options) => {
2169
+ signals.push(options.signal);
2170
+ return Promise.resolve({
2171
+ contextUrl: null,
2172
+ document: {},
2173
+ documentUrl: url
2174
+ });
2175
+ }, 50);
2176
+ (0, node_assert.deepStrictEqual)((await Promise.all([loader("https://example.com/a"), loader("https://example.com/b")])).map((r) => r.documentUrl), ["https://example.com/a", "https://example.com/b"]);
2177
+ (0, node_assert.ok)(signals[0] !== signals[1]);
2178
+ await new Promise((resolve) => setTimeout(resolve, 100));
2179
+ (0, node_assert.ok)(signals.every((signal) => !signal.aborted));
2180
+ const inner = () => Promise.reject(/* @__PURE__ */ new Error("unused"));
2181
+ (0, node_assert.ok)(require_docloader.withDocumentLoaderTimeout(inner, null) === inner);
2182
+ });
2183
+ (0, _fedify_fixture.test)("getRemoteDocument() keeps error bodies byte for byte", async () => {
2184
+ const bytes = new Uint8Array([
2185
+ 239,
2186
+ 187,
2187
+ 191,
2188
+ 78,
2189
+ 111,
2190
+ 255,
2191
+ 254
2192
+ ]);
2193
+ const url = "https://example.com/error";
2194
+ let error;
2195
+ await (0, node_assert.rejects)(require_docloader.getRemoteDocument(url, new Response(bytes, { status: 404 }), () => Promise.reject(/* @__PURE__ */ new Error("unused"))), (e) => {
2196
+ error = e;
2197
+ return true;
2198
+ });
2199
+ (0, node_assert.ok)(error instanceof require_request.FetchError);
2200
+ (0, node_assert.ok)(error.response != null);
2201
+ (0, node_assert.deepStrictEqual)(error.response.status, 404);
2202
+ (0, node_assert.deepStrictEqual)(new Uint8Array(await error.response.arrayBuffer()), bytes);
2203
+ });
2204
+ (0, _fedify_fixture.test)("getRemoteDocument() keeps metadata of bodiless or large errors", async () => {
2205
+ const url = "https://example.com/error";
2206
+ for (const response of [new Response(null, {
2207
+ status: 304,
2208
+ headers: { ETag: "\"a\"" }
2209
+ }), new Response(/* @__PURE__ */ new Uint8Array(1048577), {
2210
+ status: 500,
2211
+ headers: { ETag: "\"a\"" }
2212
+ })]) await (0, node_assert.rejects)(require_docloader.getRemoteDocument(url, response, () => Promise.reject(/* @__PURE__ */ new Error("unused"))), (e) => {
2213
+ (0, node_assert.ok)(e instanceof require_request.FetchError);
2214
+ (0, node_assert.ok)(e.response != null);
2215
+ (0, node_assert.deepStrictEqual)(e.response.status, response.status);
2216
+ (0, node_assert.deepStrictEqual)(e.response.headers.get("ETag"), "\"a\"");
2217
+ (0, node_assert.deepStrictEqual)(e.response.body, null);
2218
+ return true;
2219
+ });
2220
+ });
2221
+ (0, _fedify_fixture.test)("getDocumentLoader() reports statuses that Response cannot hold", async () => {
2222
+ await withServer((_, response) => {
2223
+ response.writeHead(999);
2224
+ response.end("Request denied");
2225
+ }, async (baseUrl) => {
2226
+ const url = `${baseUrl}/denied`;
2227
+ const loader = require_docloader.getDocumentLoader({ allowPrivateAddress: true });
2228
+ let error;
2229
+ await (0, node_assert.rejects)(loader(url), (e) => {
2230
+ error = e;
2231
+ return true;
2232
+ });
2233
+ (0, node_assert.ok)(error instanceof require_request.FetchError, String(error));
2234
+ (0, node_assert.deepStrictEqual)(error.response?.status, 999);
2235
+ (0, node_assert.deepStrictEqual)(await error.response.text(), "Request denied");
2236
+ });
2237
+ });
2238
+ (0, _fedify_fixture.test)("getDocumentLoader() times out a stalled body of such a status", async () => {
2239
+ await withServer((_, response) => {
2240
+ response.writeHead(999);
2241
+ response.write("Request");
2242
+ }, async (baseUrl) => {
2243
+ const url = `${baseUrl}/stalled-denied`;
2244
+ await (0, node_assert.rejects)(require_docloader.getDocumentLoader({
2245
+ allowPrivateAddress: true,
2246
+ timeout: 200
2247
+ })(url), (e) => isTimeoutFetchError(e, url));
2248
+ });
2249
+ });
1597
2250
  //#endregion