@fedify/vocab-runtime 2.4.0-pr.936.41 → 2.4.1

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 (152) hide show
  1. package/deno.json +4 -2
  2. package/dist/{tests/docloader-Ck8bcBir.mjs → contexts-B09jb9Pi.cjs} +173 -274
  3. package/dist/{tests/docloader-S_gQZ2tt.cjs → contexts-DWrj4AGR.js} +169 -293
  4. package/dist/{docloader-C_dir7Xb.d.ts → docloader-BMHljyYb.d.cts} +68 -2
  5. package/dist/{docloader-D2DTRiyA.d.cts → docloader-BMHljyYb.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 +762 -4529
  20. package/dist/mod.d.cts +427 -9
  21. package/dist/mod.d.ts +427 -10
  22. package/dist/mod.js +742 -4524
  23. package/dist/portable-6Aj-7dZJ.d.cts +160 -0
  24. package/dist/portable-nkTiqH0w.d.ts +160 -0
  25. package/dist/tests/body-DpC9p-iG.mjs +130 -0
  26. package/dist/tests/body-cKLBprYm.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-C9Lj11SC.cjs +4533 -0
  32. package/dist/tests/contexts-duRA_dDC.mjs +4522 -0
  33. package/dist/tests/decimal.test.cjs +9 -7
  34. package/dist/tests/decimal.test.mjs +9 -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-DyZ1JGIl.cjs +398 -0
  42. package/dist/tests/docloader-tUmMeXjU.mjs +375 -0
  43. package/dist/tests/docloader.test.cjs +816 -39
  44. package/dist/tests/docloader.test.mjs +810 -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-CmDhHNTx.cjs +12 -0
  52. package/dist/tests/jsonld-DweIJ07b.mjs +5 -0
  53. package/dist/tests/jsonld-cache-BEXiiM-V.cjs +386 -0
  54. package/dist/tests/jsonld-cache-DFnWsLuZ.mjs +339 -0
  55. package/dist/tests/jsonld-cache.test.cjs +102 -298
  56. package/dist/tests/jsonld-cache.test.mjs +95 -289
  57. package/dist/tests/{key-_wXwomh_.cjs → key-C-AYkdJJ.cjs} +11 -4
  58. package/dist/tests/{key-CDGDH_vC.mjs → key-C2Db_TAJ.mjs} +11 -3
  59. package/dist/tests/key.test.cjs +6 -5
  60. package/dist/tests/key.test.mjs +6 -4
  61. package/dist/tests/{langstr-Di5AvKpB.mjs → langstr-DYRmJqFn.mjs} +5 -2
  62. package/dist/tests/{langstr-CbAxaeEZ.cjs → langstr-_PqsL1ae.cjs} +5 -2
  63. package/dist/tests/langstr.test.cjs +11 -4
  64. package/dist/tests/langstr.test.mjs +9 -2
  65. package/dist/tests/langtag-CUppHs_s.mjs +45 -0
  66. package/dist/tests/langtag-Czjvxfzx.cjs +50 -0
  67. package/dist/tests/langtag.test.cjs +45 -0
  68. package/dist/tests/langtag.test.d.cts +1 -0
  69. package/dist/tests/langtag.test.d.mts +1 -0
  70. package/dist/tests/langtag.test.mjs +46 -0
  71. package/dist/tests/link.test.cjs +2 -3
  72. package/dist/tests/link.test.mjs +2 -2
  73. package/dist/tests/multibase/multibase.test.cjs +8 -9
  74. package/dist/tests/multibase/multibase.test.mjs +6 -6
  75. package/dist/tests/{multibase-Bz_UUDtL.cjs → multibase-B5Mea7Ip.cjs} +19 -2
  76. package/dist/tests/{multibase-B4bvakyA.mjs → multibase-BPnF_L4e.mjs} +12 -1
  77. package/dist/tests/portable-dereference-DmTYDy2p.mjs +250 -0
  78. package/dist/tests/portable-dereference-qTLMqe2_.cjs +279 -0
  79. package/dist/tests/portable-media-5p8azal0.cjs +153 -0
  80. package/dist/tests/portable-media-B_lt9MMA.mjs +148 -0
  81. package/dist/tests/portable-media.test.cjs +222 -0
  82. package/dist/tests/portable-media.test.d.cts +1 -0
  83. package/dist/tests/portable-media.test.d.mts +1 -0
  84. package/dist/tests/portable-media.test.mjs +223 -0
  85. package/dist/tests/portable-workers.test.cjs +36 -0
  86. package/dist/tests/portable-workers.test.d.cts +2 -0
  87. package/dist/tests/portable-workers.test.d.mts +2 -0
  88. package/dist/tests/portable-workers.test.mjs +35 -0
  89. package/dist/tests/{request-uk51rkhO.cjs → request-BgjA9wyl.cjs} +10 -4
  90. package/dist/tests/{request-C8CaGwtt.mjs → request-DodZNukP.mjs} +8 -2
  91. package/dist/tests/request.test.cjs +7 -4
  92. package/dist/tests/request.test.mjs +5 -2
  93. package/dist/tests/signed-representation.test.cjs +588 -0
  94. package/dist/tests/signed-representation.test.d.cts +1 -0
  95. package/dist/tests/signed-representation.test.d.mts +1 -0
  96. package/dist/tests/signed-representation.test.mjs +589 -0
  97. package/dist/tests/temporal.test.cjs +1 -2
  98. package/dist/tests/temporal.test.mjs +1 -1
  99. package/dist/tests/url-BNakuZ8k.cjs +998 -0
  100. package/dist/tests/url-DMxmp7ZG.mjs +859 -0
  101. package/dist/tests/url.test.cjs +489 -6
  102. package/dist/tests/url.test.mjs +489 -5
  103. package/dist/url-DrGTR8yv.cjs +993 -0
  104. package/dist/url-Dzyp-NsC.js +860 -0
  105. package/package.json +27 -4
  106. package/scripts/test-bun.mjs +17 -0
  107. package/src/body.test.ts +125 -0
  108. package/src/body.ts +152 -0
  109. package/src/contexts/cid-v1.json +114 -0
  110. package/src/contexts/fep-22cd.json +21 -0
  111. package/src/contexts/fep-6757.json +12 -0
  112. package/src/contexts/joinmastodon.json +1 -1
  113. package/src/contexts/miscellany.json +17 -0
  114. package/src/contexts.ts +47 -1
  115. package/src/digest.test.ts +220 -0
  116. package/src/digest.ts +229 -0
  117. package/src/docloader.test.ts +1066 -27
  118. package/src/docloader.ts +322 -105
  119. package/src/internal/jsonld-cache.ts +97 -1
  120. package/src/internal/portable-dereference.test.ts +254 -0
  121. package/src/internal/portable-dereference.ts +1000 -0
  122. package/src/internal/signed-representation.ts +565 -0
  123. package/src/jsonld-cache.test.ts +89 -0
  124. package/src/key.test.ts +9 -0
  125. package/src/key.ts +11 -1
  126. package/src/langstr.test.ts +18 -1
  127. package/src/langstr.ts +6 -2
  128. package/src/langtag.test.ts +53 -0
  129. package/src/langtag.ts +76 -0
  130. package/src/mod.ts +29 -0
  131. package/src/multibase/multibase.test.ts +5 -5
  132. package/src/portable-media.test.ts +293 -0
  133. package/src/portable-media.ts +264 -0
  134. package/src/portable-workers.test.ts +81 -0
  135. package/src/portable.ts +180 -0
  136. package/src/preprocessor.ts +7 -0
  137. package/src/request.test.ts +10 -1
  138. package/src/request.ts +9 -1
  139. package/src/signed-representation.test.ts +338 -0
  140. package/src/url.test.ts +823 -3
  141. package/src/url.ts +700 -23
  142. package/tsdown.config.ts +2 -0
  143. package/dist/tests/url-CsOV_B_P.cjs +0 -482
  144. package/dist/tests/url-Du7RQQgP.mjs +0 -392
  145. package/dist/url-DD4F0ULf.cjs +0 -483
  146. package/dist/url-DGVbSVVi.js +0 -393
  147. /package/dist/{chunk-M78iaK0I.cjs → rolldown-runtime-B7lfambq.cjs} +0 -0
  148. /package/dist/tests/{link-NUUWCdnK.mjs → link-Cevmc87v.mjs} +0 -0
  149. /package/dist/tests/{link-FguCydMA.cjs → link-DlKm8bEr.cjs} +0 -0
  150. /package/dist/tests/{multicodec-CxGVGa91.cjs → multicodec-CLRPeW4N.cjs} +0 -0
  151. /package/dist/tests/{multicodec-CyFp54fI.mjs → multicodec-CRIj_05H.mjs} +0 -0
  152. /package/dist/tests/{chunk-C2EiDwsr.cjs → rolldown-runtime-emK7D4bc.cjs} +0 -0
@@ -1,11 +1,18 @@
1
- const require_chunk = require("./chunk-C2EiDwsr.cjs");
2
- const require_docloader = require("./docloader-S_gQZ2tt.cjs");
3
- const require_request = require("./request-uk51rkhO.cjs");
4
- const require_url = require("./url-CsOV_B_P.cjs");
1
+ const require_rolldown_runtime = require("./rolldown-runtime-emK7D4bc.cjs");
2
+ const require_request = require("./request-BgjA9wyl.cjs");
3
+ const require_contexts = require("./contexts-C9Lj11SC.cjs");
4
+ const require_url = require("./url-BNakuZ8k.cjs");
5
+ const require_docloader = require("./docloader-DyZ1JGIl.cjs");
6
+ const require_jsonld = require("./jsonld-CmDhHNTx.cjs");
7
+ let node_zlib = require("node:zlib");
8
+ let _logtape_logtape = require("@logtape/logtape");
5
9
  let node_assert = require("node:assert");
6
- let node_test = require("node:test");
10
+ let node_dns_promises = require("node:dns/promises");
11
+ node_dns_promises = require_rolldown_runtime.__toESM(node_dns_promises, 1);
12
+ let _fedify_fixture = require("@fedify/fixture");
13
+ let node_http = require("node:http");
7
14
  //#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) => {
15
+ var require_glob_to_regexp = /* @__PURE__ */ require_rolldown_runtime.__commonJSMin(((exports, module) => {
9
16
  module.exports = function(glob, opts) {
10
17
  if (typeof glob !== "string") throw new TypeError("Expected a string");
11
18
  var str = String(glob);
@@ -79,7 +86,7 @@ var require_glob_to_regexp = /* @__PURE__ */ require_chunk.__commonJSMin(((expor
79
86
  }));
80
87
  //#endregion
81
88
  //#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);
89
+ var import_glob_to_regexp = /* @__PURE__ */ require_rolldown_runtime.__toESM(require_glob_to_regexp(), 1);
83
90
  /**
84
91
  * @param {string|RegExp} input The route pattern
85
92
  * @param {boolean} [loose] Allow open-ended matching. Ignored with `RegExp` input.
@@ -111,27 +118,27 @@ function parse(input, loose) {
111
118
  }
112
119
  //#endregion
113
120
  //#region ../../node_modules/.pnpm/fetch-mock@12.6.0/node_modules/fetch-mock/dist/esm/TypeDescriptor.js
114
- const valueTypes = new Set([
121
+ const valueTypes = /* @__PURE__ */ new Set([
115
122
  "boolean",
116
123
  "number",
117
124
  "null",
118
125
  "string",
119
126
  "undefined"
120
127
  ]);
121
- const referenceTypes = new Set([
128
+ const referenceTypes = /* @__PURE__ */ new Set([
122
129
  "array",
123
130
  "function",
124
131
  "object",
125
132
  "symbol"
126
133
  ]);
127
- const detectableTypes = new Set([
134
+ const detectableTypes = /* @__PURE__ */ new Set([
128
135
  "boolean",
129
136
  "function",
130
137
  "number",
131
138
  "string",
132
139
  "symbol"
133
140
  ]);
134
- const typeConstructors = new Set([
141
+ const typeConstructors = /* @__PURE__ */ new Set([
135
142
  Boolean,
136
143
  Number,
137
144
  String
@@ -202,7 +209,7 @@ var TypeDescriptor = class TypeDescriptor {
202
209
  };
203
210
  //#endregion
204
211
  //#region ../../node_modules/.pnpm/fetch-mock@12.6.0/node_modules/fetch-mock/dist/esm/IsSubsetOf.js
205
- const allowedTypes = new Set([
212
+ const allowedTypes = /* @__PURE__ */ new Set([
206
213
  "array",
207
214
  "object",
208
215
  "function",
@@ -694,7 +701,14 @@ var __classPrivateFieldGet = function(receiver, state, kind, f) {
694
701
  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
702
  return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
696
703
  };
697
- var _Route_instances, _a, _Route_responseSubscriptions, _Route_validate, _Route_sanitize, _Route_generateMatcher, _Route_limit, _Route_delayResponse;
704
+ var _Route_instances;
705
+ var _a;
706
+ var _Route_responseSubscriptions;
707
+ var _Route_validate;
708
+ var _Route_sanitize;
709
+ var _Route_generateMatcher;
710
+ var _Route_limit;
711
+ var _Route_delayResponse;
698
712
  var RouteConfigWrapper = class {
699
713
  constructor(config) {
700
714
  Object.assign(this, config);
@@ -1206,7 +1220,53 @@ var esm_default = new class FetchMock {
1206
1220
  }({ ...defaultFetchMockConfig });
1207
1221
  //#endregion
1208
1222
  //#region src/docloader.test.ts
1209
- (0, node_test.test)("new FetchError()", () => {
1223
+ (0, _fedify_fixture.test)("getRemoteDocument() forwards options to alternate documents", async () => {
1224
+ const records = [];
1225
+ await (0, _logtape_logtape.reset)();
1226
+ await (0, _logtape_logtape.configure)({
1227
+ sinks: { capture: (record) => records.push(record) },
1228
+ loggers: [{
1229
+ category: ["fedify"],
1230
+ lowestLevel: "debug",
1231
+ sinks: ["capture"]
1232
+ }]
1233
+ });
1234
+ try {
1235
+ for (const linkHeader of [true, false]) for (const suppressError of [
1236
+ true,
1237
+ false,
1238
+ void 0
1239
+ ]) {
1240
+ records.length = 0;
1241
+ const url = "https://example.com/alternate-source";
1242
+ const alternateUrl = "https://example.com/missing-alternate";
1243
+ const options = {
1244
+ suppressError,
1245
+ signal: new AbortController().signal
1246
+ };
1247
+ const response = new Response(`<link rel="alternate" type="application/activity+json" href="${alternateUrl}">`, { headers: {
1248
+ "Content-Type": "text/html",
1249
+ ...linkHeader ? { Link: `<${alternateUrl}>; rel="alternate"; type="application/activity+json"` } : {}
1250
+ } });
1251
+ let alternateRequests = 0;
1252
+ await (0, node_assert.rejects)(() => require_docloader.getRemoteDocument(url, response, async (requestedUrl, forwarded) => {
1253
+ alternateRequests++;
1254
+ (0, node_assert.deepStrictEqual)(requestedUrl, alternateUrl);
1255
+ (0, node_assert.deepStrictEqual)(forwarded, options);
1256
+ return await require_docloader.getRemoteDocument(requestedUrl, new Response(null, { status: 404 }), () => {
1257
+ throw new Error("Unexpected additional alternate");
1258
+ }, forwarded);
1259
+ }, options), require_request.FetchError);
1260
+ (0, node_assert.deepStrictEqual)(alternateRequests, 1);
1261
+ const failures = records.filter((record) => record.rawMessage === "Failed to fetch document: {status} {url} {headers}");
1262
+ (0, node_assert.deepStrictEqual)(failures.length, 1);
1263
+ (0, node_assert.deepStrictEqual)(failures[0].level, suppressError ? "warning" : "error");
1264
+ }
1265
+ } finally {
1266
+ await (0, _logtape_logtape.reset)();
1267
+ }
1268
+ });
1269
+ (0, _fedify_fixture.test)("new FetchError()", () => {
1210
1270
  const e = new require_request.FetchError("https://example.com/", "An error message.");
1211
1271
  (0, node_assert.deepStrictEqual)(e.name, "FetchError");
1212
1272
  (0, node_assert.deepStrictEqual)(e.url, new URL("https://example.com/"));
@@ -1219,7 +1279,7 @@ var esm_default = new class FetchMock {
1219
1279
  (0, node_assert.ok)(e2.response != null);
1220
1280
  (0, node_assert.deepStrictEqual)(e2.response.status, 410);
1221
1281
  });
1222
- (0, node_test.test)("getDocumentLoader()", async (t) => {
1282
+ (0, _fedify_fixture.test)("getDocumentLoader()", async (t) => {
1223
1283
  const fetchDocumentLoader = require_docloader.getDocumentLoader();
1224
1284
  esm_default.spyGlobal();
1225
1285
  esm_default.get("https://example.com/object", { body: {
@@ -1228,7 +1288,7 @@ var esm_default = new class FetchMock {
1228
1288
  name: "Fetched object",
1229
1289
  type: "Object"
1230
1290
  } });
1231
- await t.test("ok", async () => {
1291
+ await t.step("ok", async () => {
1232
1292
  (0, node_assert.deepStrictEqual)(await fetchDocumentLoader("https://example.com/object"), {
1233
1293
  contextUrl: null,
1234
1294
  documentUrl: "https://example.com/object",
@@ -1271,7 +1331,7 @@ var esm_default = new class FetchMock {
1271
1331
  Link: "<https://example.com/object>; rel=\"alternate\"; type=\"application/ld+json; profile=\"https://www.w3.org/ns/activitystreams\"\""
1272
1332
  }
1273
1333
  });
1274
- await t.test("Link header", async () => {
1334
+ await t.step("Link header", async () => {
1275
1335
  (0, node_assert.deepStrictEqual)(await fetchDocumentLoader("https://example.com/link-ctx"), {
1276
1336
  contextUrl: "https://www.w3.org/ns/activitystreams",
1277
1337
  documentUrl: "https://example.com/link-ctx",
@@ -1292,7 +1352,7 @@ var esm_default = new class FetchMock {
1292
1352
  }
1293
1353
  });
1294
1354
  });
1295
- await t.test("Link header relative url", async () => {
1355
+ await t.step("Link header relative url", async () => {
1296
1356
  (0, node_assert.deepStrictEqual)(await fetchDocumentLoader("https://example.com/link-ctx"), {
1297
1357
  contextUrl: "https://www.w3.org/ns/activitystreams",
1298
1358
  documentUrl: "https://example.com/link-ctx",
@@ -1313,7 +1373,7 @@ var esm_default = new class FetchMock {
1313
1373
  }
1314
1374
  });
1315
1375
  });
1316
- await t.test("wrong Link header syntax", async () => {
1376
+ await t.step("wrong Link header syntax", async () => {
1317
1377
  (0, node_assert.deepStrictEqual)(await fetchDocumentLoader("https://example.com/obj-w-wrong-link"), {
1318
1378
  contextUrl: null,
1319
1379
  documentUrl: "https://example.com/obj-w-wrong-link",
@@ -1337,7 +1397,7 @@ var esm_default = new class FetchMock {
1337
1397
  </html>`,
1338
1398
  headers: { "Content-Type": "text/html; charset=utf-8" }
1339
1399
  });
1340
- await t.test("HTML <link>", async () => {
1400
+ await t.step("HTML <link>", async () => {
1341
1401
  (0, node_assert.deepStrictEqual)(await fetchDocumentLoader("https://example.com/html-link"), {
1342
1402
  contextUrl: null,
1343
1403
  documentUrl: "https://example.com/object",
@@ -1361,7 +1421,7 @@ var esm_default = new class FetchMock {
1361
1421
  </html>`,
1362
1422
  headers: { "Content-Type": "application/xhtml+xml; charset=utf-8" }
1363
1423
  });
1364
- await t.test("XHTML <link>", async () => {
1424
+ await t.step("XHTML <link>", async () => {
1365
1425
  (0, node_assert.deepStrictEqual)(await fetchDocumentLoader("https://example.com/xhtml-link"), {
1366
1426
  contextUrl: null,
1367
1427
  documentUrl: "https://example.com/object",
@@ -1387,7 +1447,7 @@ var esm_default = new class FetchMock {
1387
1447
  </html>`,
1388
1448
  headers: { "Content-Type": "text/html; charset=utf-8" }
1389
1449
  });
1390
- await t.test("HTML <a>", async () => {
1450
+ await t.step("HTML <a>", async () => {
1391
1451
  (0, node_assert.deepStrictEqual)(await fetchDocumentLoader("https://example.com/html-a"), {
1392
1452
  contextUrl: null,
1393
1453
  documentUrl: "https://example.com/object",
@@ -1409,7 +1469,7 @@ var esm_default = new class FetchMock {
1409
1469
  </html>`,
1410
1470
  headers: { "Content-Type": "text/html; charset=utf-8" }
1411
1471
  });
1412
- await t.test("HTML without ActivityPub alternate link", async () => {
1472
+ await t.step("HTML without ActivityPub alternate link", async () => {
1413
1473
  await (0, node_assert.rejects)(() => fetchDocumentLoader("https://example.com/html-no-alternate"), (error) => {
1414
1474
  (0, node_assert.ok)(error instanceof require_request.FetchError);
1415
1475
  (0, node_assert.ok)(error.message.includes("HTML document has no ActivityPub alternate link"));
@@ -1429,7 +1489,7 @@ var esm_default = new class FetchMock {
1429
1489
  },
1430
1490
  headers: { "Content-Type": "text/html; charset=utf-8" }
1431
1491
  });
1432
- await t.test("wrong Content-Type with JSON body", async () => {
1492
+ await t.step("wrong Content-Type with JSON body", async () => {
1433
1493
  (0, node_assert.deepStrictEqual)(await fetchDocumentLoader("https://example.com/wrong-content-type"), {
1434
1494
  contextUrl: null,
1435
1495
  documentUrl: "https://example.com/wrong-content-type",
@@ -1448,7 +1508,7 @@ var esm_default = new class FetchMock {
1448
1508
  "Content-Type": "text/html; charset=utf-8"
1449
1509
  }
1450
1510
  });
1451
- await t.test("HTML Content-Length over limit", async () => {
1511
+ await t.step("HTML Content-Length over limit", async () => {
1452
1512
  await (0, node_assert.rejects)(() => fetchDocumentLoader("https://example.com/large-html"), (error) => {
1453
1513
  (0, node_assert.ok)(error instanceof require_request.FetchError);
1454
1514
  (0, node_assert.ok)(error.message.includes("HTML document is too large to scan for an ActivityPub alternate link"));
@@ -1458,32 +1518,211 @@ var esm_default = new class FetchMock {
1458
1518
  return true;
1459
1519
  });
1460
1520
  });
1461
- await t.test("HTML Content-Length over limit cancels body", async () => {
1521
+ await t.step("HTML Content-Length over limit cancels body", async () => {
1462
1522
  let canceled = false;
1463
1523
  const response = new Response("<!DOCTYPE html>", { headers: {
1464
1524
  "Content-Length": String(1048577),
1465
1525
  "Content-Type": "text/html; charset=utf-8"
1466
1526
  } });
1467
- Object.defineProperty(response, "body", { value: { cancel: () => {
1527
+ Object.defineProperty(response, "body", { value: new ReadableStream({ cancel() {
1468
1528
  canceled = true;
1469
- } } });
1529
+ } }) });
1470
1530
  await (0, node_assert.rejects)(() => require_docloader.getRemoteDocument("https://example.com/large-html-cancel", response, () => {
1471
1531
  throw new Error("unexpected alternate fetch");
1472
1532
  }), require_request.FetchError);
1473
1533
  (0, node_assert.deepStrictEqual)(canceled, true);
1474
1534
  });
1475
1535
  esm_default.get("https://example.com/404", { status: 404 });
1476
- await t.test("not ok", async () => {
1536
+ await t.step("not ok", async () => {
1477
1537
  await (0, node_assert.rejects)(() => fetchDocumentLoader("https://example.com/404"), require_request.FetchError, "HTTP 404: https://example.com/404");
1478
1538
  });
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), {
1539
+ await t.step("preloaded contexts", async () => {
1540
+ for (const [url, document] of Object.entries(require_contexts.preloadedContexts)) (0, node_assert.deepStrictEqual)(await fetchDocumentLoader(url), {
1481
1541
  contextUrl: null,
1482
1542
  documentUrl: url,
1483
1543
  document
1484
1544
  });
1485
1545
  });
1486
- await t.test("deny non-HTTP/HTTPS", async () => {
1546
+ await t.step("preloaded Mastodon attribution domains", async () => {
1547
+ const context = ["https://www.w3.org/ns/activitystreams", "http://joinmastodon.org/ns"];
1548
+ const expanded = await require_jsonld.jsonld_default.expand({
1549
+ "@context": context,
1550
+ id: "https://social.example/users/alice",
1551
+ type: "Person",
1552
+ attributionDomains: ["blog.example"]
1553
+ }, { documentLoader: fetchDocumentLoader });
1554
+ (0, node_assert.deepStrictEqual)(expanded, [{
1555
+ "@id": "https://social.example/users/alice",
1556
+ "@type": ["https://www.w3.org/ns/activitystreams#Person"],
1557
+ "http://joinmastodon.org/ns#attributionDomains": [{ "@value": "blog.example" }]
1558
+ }]);
1559
+ (0, node_assert.deepStrictEqual)((await require_jsonld.jsonld_default.compact(expanded, context, { documentLoader: fetchDocumentLoader })).attributionDomains, ["blog.example"]);
1560
+ });
1561
+ esm_default.get("https://www.w3.org/ns/cid/v1", { status: 503 });
1562
+ await t.step("preloaded CID v1 context", async () => {
1563
+ const url = "https://www.w3.org/ns/cid/v1";
1564
+ (0, node_assert.deepStrictEqual)(await fetchDocumentLoader(url), {
1565
+ contextUrl: null,
1566
+ documentUrl: url,
1567
+ document: require_contexts.cid_v1_default
1568
+ });
1569
+ });
1570
+ await t.step("preloaded FEP-ef61 context", async () => {
1571
+ const url = "https://w3id.org/fep/ef61";
1572
+ (0, node_assert.ok)(url in require_contexts.preloadedContexts);
1573
+ (0, node_assert.deepStrictEqual)(await fetchDocumentLoader(url), {
1574
+ contextUrl: null,
1575
+ documentUrl: url,
1576
+ document: { "@context": {
1577
+ gateways: {
1578
+ "@id": "https://w3id.org/fep/ef61/gateways",
1579
+ "@type": "@id",
1580
+ "@container": "@list"
1581
+ },
1582
+ digestMultibase: "https://www.w3.org/ns/credentials/v2#digestMultibase"
1583
+ } }
1584
+ });
1585
+ });
1586
+ esm_default.get("https://w3id.org/fep/7aa9", { status: 502 });
1587
+ await t.step("preloaded FEP-7aa9 context", async () => {
1588
+ const url = "https://w3id.org/fep/7aa9";
1589
+ (0, node_assert.deepStrictEqual)(await fetchDocumentLoader(url), {
1590
+ contextUrl: null,
1591
+ documentUrl: url,
1592
+ document: { "@context": {
1593
+ "FeaturedCollection": "https://w3id.org/fep/7aa9#FeaturedCollection",
1594
+ "FeaturedItem": "https://w3id.org/fep/7aa9#FeaturedItem",
1595
+ "FeatureRequest": "https://w3id.org/fep/7aa9#FeatureRequest",
1596
+ "FeatureAuthorization": "https://w3id.org/fep/7aa9#FeatureAuthorization",
1597
+ "topic": {
1598
+ "@id": "https://w3id.org/fep/7aa9#topic",
1599
+ "@type": "@id"
1600
+ },
1601
+ "featuredObject": {
1602
+ "@id": "https://w3id.org/fep/7aa9#featuredObject",
1603
+ "@type": "@id"
1604
+ },
1605
+ "canFeature": {
1606
+ "@id": "https://w3id.org/fep/7aa9#canFeature",
1607
+ "@type": "@id"
1608
+ },
1609
+ "featureAuthorization": {
1610
+ "@id": "https://w3id.org/fep/7aa9#featureAuthorization",
1611
+ "@type": "@id"
1612
+ }
1613
+ } }
1614
+ });
1615
+ (0, node_assert.deepStrictEqual)(esm_default.callHistory.calls(url).length, 0);
1616
+ });
1617
+ esm_default.get("https://w3id.org/fep/6757", { status: 502 });
1618
+ await t.step("preloaded FEP-6757 context", async () => {
1619
+ const url = "https://w3id.org/fep/6757";
1620
+ (0, node_assert.deepStrictEqual)(await fetchDocumentLoader(url), {
1621
+ contextUrl: null,
1622
+ documentUrl: url,
1623
+ document: { "@context": {
1624
+ license: {
1625
+ "@id": "http://purl.org/dc/terms/license",
1626
+ "@type": "@id"
1627
+ },
1628
+ preferredLicense: {
1629
+ "@id": "https://w3id.org/fep/6757#preferredLicense",
1630
+ "@type": "@id"
1631
+ }
1632
+ } }
1633
+ });
1634
+ (0, node_assert.deepStrictEqual)(await require_jsonld.jsonld_default.expand({
1635
+ "@context": ["https://www.w3.org/ns/activitystreams", url],
1636
+ type: "Person",
1637
+ license: "https://creativecommons.org/licenses/by/4.0/",
1638
+ preferredLicense: "https://creativecommons.org/licenses/by-nc/4.0/"
1639
+ }, { documentLoader: fetchDocumentLoader }), [{
1640
+ "@type": ["https://www.w3.org/ns/activitystreams#Person"],
1641
+ "http://purl.org/dc/terms/license": [{ "@id": "https://creativecommons.org/licenses/by/4.0/" }],
1642
+ "https://w3id.org/fep/6757#preferredLicense": [{ "@id": "https://creativecommons.org/licenses/by-nc/4.0/" }]
1643
+ }]);
1644
+ (0, node_assert.deepStrictEqual)(esm_default.callHistory.calls(url).length, 0);
1645
+ });
1646
+ esm_default.get("https://w3id.org/fep/22cd", { status: 502 });
1647
+ await t.step("preloaded FEP-22cd context", async () => {
1648
+ const url = "https://w3id.org/fep/22cd";
1649
+ const articleId = "https://example.com/articles/1";
1650
+ const translatorId = "https://example.com/users/alice";
1651
+ const revisionId = "https://example.com/articles/1/revisions/1";
1652
+ (0, node_assert.deepStrictEqual)(await fetchDocumentLoader(url), {
1653
+ contextUrl: null,
1654
+ documentUrl: url,
1655
+ document: { "@context": {
1656
+ "fep-22cd": "https://w3id.org/fep/22cd#",
1657
+ schema: "https://schema.org/",
1658
+ xsd: "http://www.w3.org/2001/XMLSchema#",
1659
+ translations: {
1660
+ "@id": "fep-22cd:translations",
1661
+ "@container": "@set"
1662
+ },
1663
+ Translation: "fep-22cd:Translation",
1664
+ sourceUpdated: {
1665
+ "@id": "fep-22cd:sourceUpdated",
1666
+ "@type": "xsd:dateTime"
1667
+ },
1668
+ translator: {
1669
+ "@id": "schema:translator",
1670
+ "@type": "@id",
1671
+ "@container": "@set"
1672
+ },
1673
+ inLanguage: "schema:inLanguage",
1674
+ translationOfWork: {
1675
+ "@id": "schema:translationOfWork",
1676
+ "@type": "@id"
1677
+ },
1678
+ isBasedOn: {
1679
+ "@id": "schema:isBasedOn",
1680
+ "@type": "@id"
1681
+ }
1682
+ } }
1683
+ });
1684
+ (0, node_assert.deepStrictEqual)(await require_jsonld.jsonld_default.expand({
1685
+ "@context": ["https://www.w3.org/ns/activitystreams", url],
1686
+ id: articleId,
1687
+ type: "Article",
1688
+ contentMap: {
1689
+ en: "Original",
1690
+ ko: "Translation"
1691
+ },
1692
+ translations: [{
1693
+ type: "Translation",
1694
+ inLanguage: "ko",
1695
+ translator: [translatorId],
1696
+ translationOfWork: articleId,
1697
+ sourceUpdated: "2026-09-01T00:00:00Z",
1698
+ isBasedOn: revisionId
1699
+ }]
1700
+ }, { documentLoader: fetchDocumentLoader }), [{
1701
+ "@id": articleId,
1702
+ "@type": ["https://www.w3.org/ns/activitystreams#Article"],
1703
+ "https://www.w3.org/ns/activitystreams#content": [{
1704
+ "@language": "en",
1705
+ "@value": "Original"
1706
+ }, {
1707
+ "@language": "ko",
1708
+ "@value": "Translation"
1709
+ }],
1710
+ "https://w3id.org/fep/22cd#translations": [{
1711
+ "@type": ["https://w3id.org/fep/22cd#Translation"],
1712
+ "https://schema.org/inLanguage": [{ "@value": "ko" }],
1713
+ "https://schema.org/translator": [{ "@id": translatorId }],
1714
+ "https://schema.org/translationOfWork": [{ "@id": articleId }],
1715
+ "https://w3id.org/fep/22cd#sourceUpdated": [{
1716
+ "@type": "http://www.w3.org/2001/XMLSchema#dateTime",
1717
+ "@value": "2026-09-01T00:00:00Z"
1718
+ }],
1719
+ "https://schema.org/isBasedOn": [{ "@id": revisionId }]
1720
+ }]
1721
+ }]);
1722
+ (0, node_assert.deepStrictEqual)(esm_default.callHistory.calls(url).length, 0);
1723
+ (0, node_assert.deepStrictEqual)(esm_default.callHistory.calls("https://www.w3.org/ns/activitystreams").length, 0);
1724
+ });
1725
+ await t.step("deny non-HTTP/HTTPS", async () => {
1487
1726
  await (0, node_assert.rejects)(() => fetchDocumentLoader("ftp://localhost"), require_url.UrlError);
1488
1727
  });
1489
1728
  esm_default.get("https://example.com/localhost-redirect", {
@@ -1508,13 +1747,13 @@ var esm_default = new class FetchMock {
1508
1747
  name: "Fetched object",
1509
1748
  type: "Object"
1510
1749
  } });
1511
- await t.test("allowPrivateAddress: false", async () => {
1750
+ await t.step("allowPrivateAddress: false", async () => {
1512
1751
  await (0, node_assert.rejects)(() => fetchDocumentLoader("https://localhost/object"), require_url.UrlError);
1513
1752
  await (0, node_assert.rejects)(() => fetchDocumentLoader("https://example.com/localhost-redirect"), require_url.UrlError);
1514
1753
  await (0, node_assert.rejects)(() => fetchDocumentLoader("https://example.com/localhost-link"), require_url.UrlError);
1515
1754
  });
1516
1755
  const fetchDocumentLoader2 = require_docloader.getDocumentLoader({ allowPrivateAddress: true });
1517
- await t.test("allowPrivateAddress: true", async () => {
1756
+ await t.step("allowPrivateAddress: true", async () => {
1518
1757
  const expected = {
1519
1758
  contextUrl: null,
1520
1759
  documentUrl: "https://localhost/object",
@@ -1537,12 +1776,12 @@ var esm_default = new class FetchMock {
1537
1776
  headers: { Location: `https://example.com/too-many-redirects/${Number(cl.url.split("/").at(-1)) + 1}` }
1538
1777
  };
1539
1778
  });
1540
- await t.test("too many redirects", async () => {
1779
+ await t.step("too many redirects", async () => {
1541
1780
  redirectAttempts = 0;
1542
1781
  await (0, node_assert.rejects)(() => fetchDocumentLoader("https://example.com/too-many-redirects/0"), require_request.FetchError, "Too many redirections");
1543
1782
  (0, node_assert.deepStrictEqual)(redirectAttempts, 21);
1544
1783
  });
1545
- await t.test("custom max redirection", async () => {
1784
+ await t.step("custom max redirection", async () => {
1546
1785
  redirectAttempts = 0;
1547
1786
  const loader = require_docloader.getDocumentLoader({ maxRedirection: 1 });
1548
1787
  await (0, node_assert.rejects)(() => loader("https://example.com/too-many-redirects/0"), require_request.FetchError, "Too many redirections");
@@ -1563,7 +1802,7 @@ var esm_default = new class FetchMock {
1563
1802
  headers: { Location: "https://example.com/redirect-loop-a" }
1564
1803
  };
1565
1804
  });
1566
- await t.test("redirect loop", async () => {
1805
+ await t.step("redirect loop", async () => {
1567
1806
  loopAttempts = 0;
1568
1807
  await (0, node_assert.rejects)(() => fetchDocumentLoader("https://example.com/redirect-loop-a"), require_request.FetchError, "Redirect loop detected");
1569
1808
  (0, node_assert.deepStrictEqual)(loopAttempts, 2);
@@ -1576,7 +1815,7 @@ var esm_default = new class FetchMock {
1576
1815
  headers: { Location: "/redirect-loop-relative" }
1577
1816
  };
1578
1817
  });
1579
- await t.test("redirect loop with relative location", async () => {
1818
+ await t.step("redirect loop with relative location", async () => {
1580
1819
  relativeLoopAttempts = 0;
1581
1820
  await (0, node_assert.rejects)(() => fetchDocumentLoader("https://example.com/redirect-loop-relative"), require_request.FetchError, "Redirect loop detected");
1582
1821
  (0, node_assert.deepStrictEqual)(relativeLoopAttempts, 1);
@@ -1586,7 +1825,7 @@ var esm_default = new class FetchMock {
1586
1825
  body: maliciousPayload,
1587
1826
  headers: { "Content-Type": "text/html; charset=utf-8" }
1588
1827
  });
1589
- await t.test("ReDoS resistance (CVE-2025-68475)", async () => {
1828
+ await t.step("ReDoS resistance (CVE-2025-68475)", async () => {
1590
1829
  const start = performance.now();
1591
1830
  await (0, node_assert.rejects)(() => fetchDocumentLoader("https://example.com/redos"), require_request.FetchError);
1592
1831
  const elapsed = performance.now() - start;
@@ -1594,4 +1833,542 @@ var esm_default = new class FetchMock {
1594
1833
  });
1595
1834
  esm_default.hardReset();
1596
1835
  });
1836
+ (0, _fedify_fixture.test)("getDocumentLoader() bounds JSON, HTML, and alternate documents", async () => {
1837
+ const url = "https://example.com/bounded";
1838
+ esm_default.mockGlobal();
1839
+ let oversized = true;
1840
+ try {
1841
+ esm_default.get(url, () => new Response("{\"name\":\"hello\"}", { headers: {
1842
+ "Content-Type": "application/activity+json",
1843
+ ...oversized ? { "Content-Length": String(16777217) } : {}
1844
+ } }));
1845
+ const loader = require_docloader.getDocumentLoader({ allowPrivateAddress: true });
1846
+ await (0, node_assert.rejects)(loader(url), require_request.FetchError);
1847
+ oversized = false;
1848
+ (0, node_assert.deepStrictEqual)((await loader(url)).document, { name: "hello" });
1849
+ oversized = true;
1850
+ esm_default.get(`${url}/html`, () => new Response(" ".repeat(1048577), { headers: { "Content-Type": "text/html" } }));
1851
+ await (0, node_assert.rejects)(require_docloader.getDocumentLoader({ allowPrivateAddress: true })(`${url}/html`), require_request.FetchError);
1852
+ esm_default.get(`${url}/alternate`, () => new Response("", { headers: {
1853
+ "Content-Type": "text/html",
1854
+ Link: `<${url}>; rel="alternate"; type="application/activity+json"`
1855
+ } }));
1856
+ await (0, node_assert.rejects)(loader(`${url}/alternate`), require_request.FetchError);
1857
+ esm_default.get(`${url}/redirect`, {
1858
+ status: 302,
1859
+ headers: { Location: url }
1860
+ });
1861
+ await (0, node_assert.rejects)(loader(`${url}/redirect`), require_request.FetchError);
1862
+ } finally {
1863
+ esm_default.hardReset();
1864
+ }
1865
+ });
1866
+ (0, _fedify_fixture.test)("getDocumentLoader() rejects oversized gzip responses from fetch", async () => {
1867
+ const document = { name: "a".repeat(16 * 1024 * 1024) };
1868
+ const compressed = (0, node_zlib.gzipSync)(JSON.stringify(document));
1869
+ (0, node_assert.ok)(compressed.byteLength < 64 * 1024);
1870
+ const server = (0, node_http.createServer)((request, response) => {
1871
+ const body = request.url === "/small" ? (0, node_zlib.gzipSync)("{\"name\":\"hello\"}") : compressed;
1872
+ response.writeHead(200, {
1873
+ "Content-Type": "application/activity+json",
1874
+ "Content-Encoding": "gzip",
1875
+ "Content-Length": body.byteLength
1876
+ });
1877
+ response.end(body);
1878
+ });
1879
+ await new Promise((resolve, reject) => {
1880
+ server.once("error", reject);
1881
+ server.listen(0, "127.0.0.1", resolve);
1882
+ });
1883
+ try {
1884
+ const address = server.address();
1885
+ (0, node_assert.ok)(address != null && typeof address !== "string");
1886
+ const url = `http://127.0.0.1:${address.port}/document`;
1887
+ await (0, node_assert.rejects)(require_docloader.getDocumentLoader({ allowPrivateAddress: true })(url), require_request.FetchError);
1888
+ (0, node_assert.deepStrictEqual)((await require_docloader.getDocumentLoader({ allowPrivateAddress: true })(new URL("/small", url).href)).document, { name: "hello" });
1889
+ } finally {
1890
+ await new Promise((resolve, reject) => {
1891
+ server.close((error) => error == null ? resolve() : reject(error));
1892
+ server.closeAllConnections();
1893
+ });
1894
+ }
1895
+ });
1896
+ (0, _fedify_fixture.test)("getRemoteDocument() bounds JSON by default", async () => {
1897
+ let canceled = false;
1898
+ let pulls = 0;
1899
+ await (0, node_assert.rejects)(require_docloader.getRemoteDocument("https://example.com/document", new Response(new ReadableStream({
1900
+ pull(controller) {
1901
+ pulls++;
1902
+ controller.enqueue(new Uint8Array(64 * 1024).fill(32));
1903
+ },
1904
+ cancel() {
1905
+ canceled = true;
1906
+ }
1907
+ }, { highWaterMark: 0 })), () => {
1908
+ throw new Error("Unexpected alternate document");
1909
+ }), require_request.FetchError);
1910
+ (0, node_assert.deepStrictEqual)(canceled, true);
1911
+ (0, node_assert.deepStrictEqual)(pulls, 257);
1912
+ });
1913
+ (0, _fedify_fixture.test)("getDocumentLoader() logs DNS failures as such", async (t) => {
1914
+ if ("Deno" in globalThis && (await Deno.permissions.query({ name: "net" })).state !== "granted") throw new Error("This DNS test requires Deno network permission (--allow-net).");
1915
+ const loader = require_docloader.getDocumentLoader();
1916
+ for (const result of [
1917
+ "throws",
1918
+ "empty",
1919
+ "private"
1920
+ ]) await t.step(result, async () => {
1921
+ const originalLookup = node_dns_promises.default.lookup;
1922
+ node_dns_promises.default.lookup = (() => result === "throws" ? Promise.reject(/* @__PURE__ */ new Error("Resolver unavailable")) : Promise.resolve(result === "empty" ? [] : [{
1923
+ address: "127.0.0.1",
1924
+ family: 4
1925
+ }]));
1926
+ const records = [];
1927
+ await (0, _logtape_logtape.configure)({
1928
+ sinks: { buffer: (record) => records.push(record) },
1929
+ loggers: [{
1930
+ category: "fedify",
1931
+ sinks: ["buffer"],
1932
+ lowestLevel: "debug"
1933
+ }, {
1934
+ category: ["logtape", "meta"],
1935
+ sinks: []
1936
+ }],
1937
+ reset: true
1938
+ });
1939
+ try {
1940
+ const url = "https://dns-failure.invalid/object";
1941
+ let error;
1942
+ await (0, node_assert.rejects)(() => loader(url), (e) => {
1943
+ error = e;
1944
+ return true;
1945
+ });
1946
+ (0, node_assert.ok)(error instanceof require_url.UrlError);
1947
+ (0, node_assert.deepStrictEqual)(error.reason, result === "private" ? "disallowed" : "dns");
1948
+ (0, node_assert.deepStrictEqual)(records.map((r) => [
1949
+ r.level,
1950
+ r.rawMessage,
1951
+ r.properties.url
1952
+ ]), [result === "private" ? [
1953
+ "error",
1954
+ "Disallowed private URL: {url}",
1955
+ url
1956
+ ] : [
1957
+ "debug",
1958
+ "DNS lookup failed for {url}",
1959
+ url
1960
+ ]]);
1961
+ (0, node_assert.ok)(records[0].properties.error === error);
1962
+ } finally {
1963
+ await (0, _logtape_logtape.reset)();
1964
+ node_dns_promises.default.lookup = originalLookup;
1965
+ }
1966
+ });
1967
+ });
1968
+ (0, _fedify_fixture.test)("getDocumentLoader() bounds alternate document chains", async (t) => {
1969
+ const base = "https://example.com/alternate-chain/";
1970
+ const loader = require_docloader.getDocumentLoader({ allowPrivateAddress: true });
1971
+ for (const mode of [
1972
+ "header",
1973
+ "html",
1974
+ "mixed"
1975
+ ]) for (const hops of [20, 21]) await t.step(`${mode}: ${hops} hops`, async () => {
1976
+ esm_default.mockGlobal();
1977
+ let requests = 0;
1978
+ esm_default.get(`begin:${base}`, ({ url }) => {
1979
+ requests++;
1980
+ const index = Number(new URL(url).pathname.split("/").at(-1));
1981
+ if (index === hops || requests > 30) return Response.json({ done: true });
1982
+ const next = `${base}${index + 1}`;
1983
+ if (mode === "mixed" && index % 2 === 1) return Response.redirect(next, 302);
1984
+ return alternate(next, mode === "html");
1985
+ });
1986
+ try {
1987
+ if (hops === 20) (0, node_assert.deepStrictEqual)((await loader(`${base}0`)).document, { done: true });
1988
+ else await (0, node_assert.rejects)(loader(`${base}0`), (error) => {
1989
+ (0, node_assert.ok)(error instanceof require_request.FetchError);
1990
+ (0, node_assert.ok)(error.message.includes("Too many redirections (21)"));
1991
+ return true;
1992
+ });
1993
+ (0, node_assert.deepStrictEqual)(requests, 21);
1994
+ } finally {
1995
+ esm_default.hardReset();
1996
+ }
1997
+ });
1998
+ for (const mode of [
1999
+ "header",
2000
+ "html",
2001
+ "alternate-redirect",
2002
+ "redirect-alternate",
2003
+ "redirect-intermediate"
2004
+ ]) await t.step(`${mode}: cycle`, async () => {
2005
+ esm_default.mockGlobal();
2006
+ let requests = 0;
2007
+ esm_default.get(`begin:${base}`, ({ url }) => {
2008
+ requests++;
2009
+ if (requests > 30) return Response.json({ stopped: true });
2010
+ const index = Number(new URL(url).pathname.split("/").at(-1));
2011
+ const next = `${base}${mode === "redirect-intermediate" ? index === 0 ? 1 : index === 1 ? 2 : 1 : 1 - index}`;
2012
+ if (mode === "alternate-redirect" && index === 1 || (mode === "redirect-alternate" || mode === "redirect-intermediate") && index === 0) return Response.redirect(next, 302);
2013
+ return alternate(next, mode === "html");
2014
+ });
2015
+ try {
2016
+ await (0, node_assert.rejects)(loader(`${base}0`), (error) => {
2017
+ (0, node_assert.ok)(error instanceof require_request.FetchError);
2018
+ (0, node_assert.ok)(error.message.includes("Redirect loop detected:"));
2019
+ return true;
2020
+ });
2021
+ (0, node_assert.deepStrictEqual)(requests, mode === "redirect-intermediate" ? 3 : 2);
2022
+ } finally {
2023
+ esm_default.hardReset();
2024
+ }
2025
+ });
2026
+ await t.step("alternate followed by 20 redirects shares the limit", async () => {
2027
+ esm_default.mockGlobal();
2028
+ let requests = 0;
2029
+ esm_default.get(`begin:${base}`, ({ url }) => {
2030
+ requests++;
2031
+ const index = Number(new URL(url).pathname.split("/").at(-1));
2032
+ if (index === 21) return Response.json({ done: true });
2033
+ return index === 0 ? alternate(`${base}1`, false) : Response.redirect(`${base}${index + 1}`, 302);
2034
+ });
2035
+ try {
2036
+ await (0, node_assert.rejects)(loader(`${base}0`), (error) => {
2037
+ (0, node_assert.ok)(error instanceof require_request.FetchError);
2038
+ (0, node_assert.ok)(error.message.includes("Too many redirections (21)"));
2039
+ return true;
2040
+ });
2041
+ (0, node_assert.deepStrictEqual)(requests, 21);
2042
+ } finally {
2043
+ esm_default.hardReset();
2044
+ }
2045
+ });
2046
+ await t.step("relative alternate after redirect and isolated calls", async () => {
2047
+ esm_default.mockGlobal();
2048
+ esm_default.get(`${base}start`, Response.redirect(`${base}html`, 302));
2049
+ esm_default.get(`${base}html`, alternate("./document", true));
2050
+ esm_default.get(`${base}document`, Response.json({ done: true }));
2051
+ try {
2052
+ for (let i = 0; i < 2; i++) {
2053
+ const results = await Promise.all([loader(`${base}start`), loader(`${base}start`)]);
2054
+ for (const result of results) {
2055
+ (0, node_assert.deepStrictEqual)(result.document, { done: true });
2056
+ (0, node_assert.deepStrictEqual)(result.documentUrl, `${base}document`);
2057
+ }
2058
+ }
2059
+ } finally {
2060
+ esm_default.hardReset();
2061
+ }
2062
+ });
2063
+ function alternate(next, html) {
2064
+ return html ? new Response(`<link rel="alternate" type="application/activity+json" href="${next}">`, { headers: { "Content-Type": "text/html" } }) : new Response("not JSON", { headers: {
2065
+ "Content-Type": "text/plain",
2066
+ Link: `<${next}>; rel="alternate"; type="application/activity+json"`
2067
+ } });
2068
+ }
2069
+ });
2070
+ (0, _fedify_fixture.test)("getDocumentLoader() preserves cancellation across alternates", async (t) => {
2071
+ const base = "https://example.com/alternate-abort/";
2072
+ const loader = require_docloader.getDocumentLoader({ allowPrivateAddress: true });
2073
+ for (const html of [false, true]) await t.step(html ? "HTML" : "Link header", async () => {
2074
+ esm_default.mockGlobal();
2075
+ const controller = new AbortController();
2076
+ let requests = 0;
2077
+ esm_default.get(`begin:${base}`, ({ url }) => {
2078
+ requests++;
2079
+ const index = Number(new URL(url).pathname.split("/").at(-1));
2080
+ if (index === 2) return Response.json({ done: true });
2081
+ if (index === 1) controller.abort();
2082
+ const next = `${base}${index + 1}`;
2083
+ return new Response(html ? `<link rel="alternate" type="application/activity+json" href="${next}">` : "not JSON", { headers: html ? { "Content-Type": "text/html" } : {
2084
+ "Content-Type": "text/plain",
2085
+ Link: `<${next}>; rel="alternate"; type="application/activity+json"`
2086
+ } });
2087
+ });
2088
+ try {
2089
+ await (0, node_assert.rejects)(loader(`${base}0`, { signal: controller.signal }), { name: "AbortError" });
2090
+ (0, node_assert.deepStrictEqual)(requests, 2);
2091
+ } finally {
2092
+ esm_default.hardReset();
2093
+ }
2094
+ });
2095
+ });
2096
+ (0, _fedify_fixture.test)("getDocumentLoader() rejects cancellation before fetching", async () => {
2097
+ esm_default.mockGlobal();
2098
+ let requests = 0;
2099
+ const url = "https://example.com/pre-aborted-alternate";
2100
+ esm_default.get(url, () => {
2101
+ requests++;
2102
+ return Response.json({ done: true });
2103
+ });
2104
+ try {
2105
+ const loader = require_docloader.getDocumentLoader({ allowPrivateAddress: true });
2106
+ const controller = new AbortController();
2107
+ controller.abort();
2108
+ await (0, node_assert.rejects)(loader(url, { signal: controller.signal }), { name: "AbortError" });
2109
+ (0, node_assert.deepStrictEqual)(requests, 0);
2110
+ } finally {
2111
+ esm_default.hardReset();
2112
+ }
2113
+ });
2114
+ async function withServer(handler, callback) {
2115
+ const server = (0, node_http.createServer)(handler);
2116
+ await new Promise((resolve, reject) => {
2117
+ server.once("error", reject);
2118
+ server.listen(0, "127.0.0.1", resolve);
2119
+ });
2120
+ try {
2121
+ const address = server.address();
2122
+ (0, node_assert.ok)(address != null && typeof address !== "string");
2123
+ return await callback(`http://127.0.0.1:${address.port}`);
2124
+ } finally {
2125
+ await new Promise((resolve, reject) => {
2126
+ server.close((error) => error == null ? resolve() : reject(error));
2127
+ server.closeAllConnections();
2128
+ });
2129
+ }
2130
+ }
2131
+ function isTimeoutFetchError(error, url) {
2132
+ (0, node_assert.ok)(error instanceof require_request.FetchError, String(error));
2133
+ (0, node_assert.deepStrictEqual)(error.url.href, url);
2134
+ (0, node_assert.deepStrictEqual)(error.response, void 0);
2135
+ (0, node_assert.ok)(error.cause instanceof DOMException);
2136
+ (0, node_assert.deepStrictEqual)(error.cause.name, "TimeoutError");
2137
+ return true;
2138
+ }
2139
+ (0, _fedify_fixture.test)("resolveDocumentLoaderTimeout() validates timeouts", () => {
2140
+ (0, node_assert.deepStrictEqual)(require_docloader.resolveDocumentLoaderTimeout(void 0), 1e4);
2141
+ (0, node_assert.deepStrictEqual)(require_docloader.resolveDocumentLoaderTimeout(null), null);
2142
+ (0, node_assert.deepStrictEqual)(require_docloader.resolveDocumentLoaderTimeout(1500), 1500);
2143
+ (0, node_assert.deepStrictEqual)(require_docloader.resolveDocumentLoaderTimeout(.5), 1);
2144
+ (0, node_assert.deepStrictEqual)(require_docloader.resolveDocumentLoaderTimeout(2147483647), 2147483647);
2145
+ for (const invalid of [
2146
+ 0,
2147
+ -1,
2148
+ NaN,
2149
+ Infinity,
2150
+ 2147483648
2151
+ ]) (0, node_assert.throws)(() => require_docloader.resolveDocumentLoaderTimeout(invalid), RangeError);
2152
+ (0, node_assert.throws)(() => require_docloader.getDocumentLoader({ timeout: 0 }), RangeError);
2153
+ });
2154
+ (0, _fedify_fixture.test)("getDocumentLoader() times out a request without a response", async () => {
2155
+ await withServer(() => {}, async (baseUrl) => {
2156
+ const url = `${baseUrl}/hang`;
2157
+ await (0, node_assert.rejects)(require_docloader.getDocumentLoader({
2158
+ allowPrivateAddress: true,
2159
+ timeout: 100
2160
+ })(url), (e) => isTimeoutFetchError(e, url));
2161
+ });
2162
+ });
2163
+ (0, _fedify_fixture.test)("getDocumentLoader() times out a stalled response body", async () => {
2164
+ for (const status of [200, 404]) await withServer((_, response) => {
2165
+ response.writeHead(status, { "Content-Type": "application/activity+json" });
2166
+ response.write("{\"id\":");
2167
+ }, async (baseUrl) => {
2168
+ const url = `${baseUrl}/stalled`;
2169
+ await (0, node_assert.rejects)(require_docloader.getDocumentLoader({
2170
+ allowPrivateAddress: true,
2171
+ timeout: 200
2172
+ })(url), (e) => isTimeoutFetchError(e, url));
2173
+ });
2174
+ });
2175
+ (0, _fedify_fixture.test)("getDocumentLoader() shares the timeout across redirects", async () => {
2176
+ let requests = 0;
2177
+ await withServer((request, response) => {
2178
+ requests++;
2179
+ const index = Number(request.url?.split("/").at(-1));
2180
+ setTimeout(() => {
2181
+ if (index >= 15) {
2182
+ response.writeHead(200, { "Content-Type": "application/activity+json" });
2183
+ response.end("{\"done\":true}");
2184
+ return;
2185
+ }
2186
+ response.writeHead(302, { Location: `/redirect/${index + 1}` });
2187
+ response.end();
2188
+ }, 50);
2189
+ }, async (baseUrl) => {
2190
+ const url = `${baseUrl}/redirect/0`;
2191
+ await (0, node_assert.rejects)(require_docloader.getDocumentLoader({
2192
+ allowPrivateAddress: true,
2193
+ timeout: 300
2194
+ })(url), (e) => isTimeoutFetchError(e, url));
2195
+ (0, node_assert.ok)(requests < 16, `requests: ${requests}`);
2196
+ (0, node_assert.deepStrictEqual)((await require_docloader.getDocumentLoader({
2197
+ allowPrivateAddress: true,
2198
+ timeout: null
2199
+ })(url)).document, { done: true });
2200
+ });
2201
+ });
2202
+ (0, _fedify_fixture.test)("getDocumentLoader() shares the timeout across alternates", async (t) => {
2203
+ for (const html of [false, true]) await t.step(html ? "HTML" : "Link header", async () => {
2204
+ await withServer((request, response) => {
2205
+ const index = Number(request.url?.split("/").at(-1));
2206
+ setTimeout(() => {
2207
+ if (index >= 15) {
2208
+ response.writeHead(200, { "Content-Type": "application/activity+json" });
2209
+ response.end("{\"done\":true}");
2210
+ return;
2211
+ }
2212
+ const next = `/alternate/${index + 1}`;
2213
+ response.writeHead(200, html ? { "Content-Type": "text/html" } : {
2214
+ "Content-Type": "text/plain",
2215
+ Link: `<${next}>; rel="alternate"; type="application/activity+json"`
2216
+ });
2217
+ response.end(html ? `<link rel="alternate" type="application/activity+json" href="${next}">` : "not JSON");
2218
+ }, 50);
2219
+ }, async (baseUrl) => {
2220
+ const url = `${baseUrl}/alternate/0`;
2221
+ await (0, node_assert.rejects)(require_docloader.getDocumentLoader({
2222
+ allowPrivateAddress: true,
2223
+ timeout: 300
2224
+ })(url), (e) => isTimeoutFetchError(e, url));
2225
+ });
2226
+ });
2227
+ });
2228
+ (0, _fedify_fixture.test)("getDocumentLoader() lets the caller's signal abort a request", async () => {
2229
+ await withServer(() => {}, async (baseUrl) => {
2230
+ const url = `${baseUrl}/hang`;
2231
+ for (const timeout of [
2232
+ void 0,
2233
+ 6e4,
2234
+ null
2235
+ ]) {
2236
+ const loader = require_docloader.getDocumentLoader({
2237
+ allowPrivateAddress: true,
2238
+ timeout
2239
+ });
2240
+ const controller = new AbortController();
2241
+ const reason = /* @__PURE__ */ new Error("Canceled by the caller");
2242
+ setTimeout(() => controller.abort(reason), 50);
2243
+ await (0, node_assert.rejects)(loader(url, { signal: controller.signal }), (e) => {
2244
+ (0, node_assert.deepStrictEqual)(e, reason);
2245
+ return true;
2246
+ });
2247
+ }
2248
+ const loader = require_docloader.getDocumentLoader({
2249
+ allowPrivateAddress: true,
2250
+ timeout: 50
2251
+ });
2252
+ const controller = new AbortController();
2253
+ const reason = /* @__PURE__ */ new Error("Canceled by the caller");
2254
+ controller.abort(reason);
2255
+ await (0, node_assert.rejects)(loader(url, { signal: controller.signal }), (e) => {
2256
+ (0, node_assert.deepStrictEqual)(e, reason);
2257
+ return true;
2258
+ });
2259
+ });
2260
+ });
2261
+ (0, _fedify_fixture.test)("getDocumentLoader() bounds DNS lookups that ignore the timeout", async () => {
2262
+ if ("Deno" in globalThis && (await Deno.permissions.query({ name: "net" })).state !== "granted") throw new Error("This DNS test requires Deno network permission (--allow-net).");
2263
+ esm_default.mockGlobal();
2264
+ let requests = 0;
2265
+ const url = "https://slow-dns.example/object";
2266
+ esm_default.get(url, () => {
2267
+ requests++;
2268
+ return Response.json({ id: url });
2269
+ });
2270
+ const originalLookup = node_dns_promises.default.lookup;
2271
+ let resolveLookup = () => {};
2272
+ const lookedUp = new Promise((resolve) => resolveLookup = resolve);
2273
+ node_dns_promises.default.lookup = (() => new Promise((resolve) => setTimeout(() => {
2274
+ resolve([{
2275
+ address: "93.184.215.14",
2276
+ family: 4
2277
+ }]);
2278
+ resolveLookup();
2279
+ }, 200)));
2280
+ try {
2281
+ await (0, node_assert.rejects)(require_docloader.getDocumentLoader({ timeout: 50 })(url), (e) => isTimeoutFetchError(e, url));
2282
+ await lookedUp;
2283
+ await new Promise((resolve) => setTimeout(resolve, 10));
2284
+ (0, node_assert.deepStrictEqual)(requests, 0);
2285
+ } finally {
2286
+ node_dns_promises.default.lookup = originalLookup;
2287
+ esm_default.hardReset();
2288
+ }
2289
+ });
2290
+ (0, _fedify_fixture.test)("withDocumentLoaderTimeout() cleans up after each call", async () => {
2291
+ const signals = [];
2292
+ const loader = require_docloader.withDocumentLoaderTimeout((url, options) => {
2293
+ signals.push(options.signal);
2294
+ return Promise.resolve({
2295
+ contextUrl: null,
2296
+ document: {},
2297
+ documentUrl: url
2298
+ });
2299
+ }, 50);
2300
+ (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"]);
2301
+ (0, node_assert.ok)(signals[0] !== signals[1]);
2302
+ await new Promise((resolve) => setTimeout(resolve, 100));
2303
+ (0, node_assert.ok)(signals.every((signal) => !signal.aborted));
2304
+ const inner = () => Promise.reject(/* @__PURE__ */ new Error("unused"));
2305
+ (0, node_assert.ok)(require_docloader.withDocumentLoaderTimeout(inner, null) === inner);
2306
+ });
2307
+ (0, _fedify_fixture.test)("getRemoteDocument() keeps error bodies byte for byte", async () => {
2308
+ const bytes = new Uint8Array([
2309
+ 239,
2310
+ 187,
2311
+ 191,
2312
+ 78,
2313
+ 111,
2314
+ 255,
2315
+ 254
2316
+ ]);
2317
+ const url = "https://example.com/error";
2318
+ let error;
2319
+ await (0, node_assert.rejects)(require_docloader.getRemoteDocument(url, new Response(bytes, { status: 404 }), () => Promise.reject(/* @__PURE__ */ new Error("unused"))), (e) => {
2320
+ error = e;
2321
+ return true;
2322
+ });
2323
+ (0, node_assert.ok)(error instanceof require_request.FetchError);
2324
+ (0, node_assert.ok)(error.response != null);
2325
+ (0, node_assert.deepStrictEqual)(error.response.status, 404);
2326
+ (0, node_assert.deepStrictEqual)(new Uint8Array(await error.response.arrayBuffer()), bytes);
2327
+ });
2328
+ (0, _fedify_fixture.test)("getRemoteDocument() keeps metadata of bodiless or large errors", async () => {
2329
+ const url = "https://example.com/error";
2330
+ for (const response of [new Response(null, {
2331
+ status: 304,
2332
+ headers: { ETag: "\"a\"" }
2333
+ }), new Response(/* @__PURE__ */ new Uint8Array(1048577), {
2334
+ status: 500,
2335
+ headers: { ETag: "\"a\"" }
2336
+ })]) await (0, node_assert.rejects)(require_docloader.getRemoteDocument(url, response, () => Promise.reject(/* @__PURE__ */ new Error("unused"))), (e) => {
2337
+ (0, node_assert.ok)(e instanceof require_request.FetchError);
2338
+ (0, node_assert.ok)(e.response != null);
2339
+ (0, node_assert.deepStrictEqual)(e.response.status, response.status);
2340
+ (0, node_assert.deepStrictEqual)(e.response.headers.get("ETag"), "\"a\"");
2341
+ (0, node_assert.deepStrictEqual)(e.response.body, null);
2342
+ return true;
2343
+ });
2344
+ });
2345
+ (0, _fedify_fixture.test)("getDocumentLoader() reports statuses that Response cannot hold", async () => {
2346
+ await withServer((_, response) => {
2347
+ response.writeHead(999);
2348
+ response.end("Request denied");
2349
+ }, async (baseUrl) => {
2350
+ const url = `${baseUrl}/denied`;
2351
+ const loader = require_docloader.getDocumentLoader({ allowPrivateAddress: true });
2352
+ let error;
2353
+ await (0, node_assert.rejects)(loader(url), (e) => {
2354
+ error = e;
2355
+ return true;
2356
+ });
2357
+ (0, node_assert.ok)(error instanceof require_request.FetchError, String(error));
2358
+ (0, node_assert.deepStrictEqual)(error.response?.status, 999);
2359
+ (0, node_assert.deepStrictEqual)(await error.response.text(), "Request denied");
2360
+ });
2361
+ });
2362
+ (0, _fedify_fixture.test)("getDocumentLoader() times out a stalled body of such a status", async () => {
2363
+ await withServer((_, response) => {
2364
+ response.writeHead(999);
2365
+ response.write("Request");
2366
+ }, async (baseUrl) => {
2367
+ const url = `${baseUrl}/stalled-denied`;
2368
+ await (0, node_assert.rejects)(require_docloader.getDocumentLoader({
2369
+ allowPrivateAddress: true,
2370
+ timeout: 200
2371
+ })(url), (e) => isTimeoutFetchError(e, url));
2372
+ });
2373
+ });
1597
2374
  //#endregion