@fedify/vocab-runtime 2.0.29-dev.2139 → 2.0.29
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/deno.json +1 -1
- package/dist/mod.cjs +10 -3
- package/dist/mod.d.cts +2 -0
- package/dist/mod.d.ts +2 -0
- package/dist/mod.js +10 -3
- package/dist/tests/{body-C1qEpLgG.mjs → body-Dg6PqC7C.mjs} +1 -1
- package/dist/tests/{body-Dcc2GQXa.cjs → body-ciFbtONg.cjs} +1 -1
- package/dist/tests/body.test.cjs +1 -1
- package/dist/tests/body.test.mjs +1 -1
- package/dist/tests/docloader.test.cjs +157 -4
- package/dist/tests/docloader.test.mjs +157 -4
- package/dist/tests/{request-BwoIVMwm.mjs → request-CHvRxsQO.mjs} +1 -1
- package/dist/tests/{request-C13rzoc1.cjs → request-D9e2fcok.cjs} +1 -1
- package/dist/tests/request.test.cjs +1 -1
- package/dist/tests/request.test.mjs +1 -1
- package/package.json +1 -1
- package/src/docloader.test.ts +205 -0
- package/src/docloader.ts +23 -2
package/deno.json
CHANGED
package/dist/mod.cjs
CHANGED
|
@@ -4460,7 +4460,7 @@ const preloadedContexts = {
|
|
|
4460
4460
|
//#endregion
|
|
4461
4461
|
//#region deno.json
|
|
4462
4462
|
var name = "@fedify/vocab-runtime";
|
|
4463
|
-
var version = "2.0.29
|
|
4463
|
+
var version = "2.0.29";
|
|
4464
4464
|
//#endregion
|
|
4465
4465
|
//#region src/request.ts
|
|
4466
4466
|
/**
|
|
@@ -5088,6 +5088,8 @@ async function getRemoteDocument(url, response, fetch) {
|
|
|
5088
5088
|
}
|
|
5089
5089
|
/**
|
|
5090
5090
|
* Creates a JSON-LD document loader that utilizes the browser's `fetch` API.
|
|
5091
|
+
* At most 20 HTTP redirects and alternate document links are followed in total
|
|
5092
|
+
* per call. Revisiting a URL within that chain throws a {@link FetchError}.
|
|
5091
5093
|
*
|
|
5092
5094
|
* The created loader preloads the below frequently used contexts by default
|
|
5093
5095
|
* (unless `options.skipPreloadedContexts` is set to `true`):
|
|
@@ -5164,7 +5166,12 @@ function getDocumentLoader({ allowPrivateAddress, skipPreloadedContexts, userAge
|
|
|
5164
5166
|
}
|
|
5165
5167
|
return await load(redirectUrl, options, redirected + 1, visited);
|
|
5166
5168
|
}
|
|
5167
|
-
const result = await getRemoteDocument(currentUrl, response,
|
|
5169
|
+
const result = await getRemoteDocument(currentUrl, response, async (alternateUrl) => {
|
|
5170
|
+
options?.signal?.throwIfAborted();
|
|
5171
|
+
if (redirected >= DEFAULT_MAX_REDIRECTION) throw new FetchError(currentUrl, `Too many redirections (${redirected + 1})`);
|
|
5172
|
+
if (visited.has(alternateUrl)) throw new FetchError(currentUrl, `Redirect loop detected: ${alternateUrl}`);
|
|
5173
|
+
return await load(alternateUrl, options, redirected + 1, visited);
|
|
5174
|
+
});
|
|
5168
5175
|
span.setAttribute("docloader.document_url", result.documentUrl);
|
|
5169
5176
|
if (result.contextUrl != null) span.setAttribute("docloader.context_url", result.contextUrl);
|
|
5170
5177
|
return result;
|
|
@@ -5180,7 +5187,7 @@ function getDocumentLoader({ allowPrivateAddress, skipPreloadedContexts, userAge
|
|
|
5180
5187
|
}
|
|
5181
5188
|
});
|
|
5182
5189
|
}
|
|
5183
|
-
return load;
|
|
5190
|
+
return (url, options) => load(url, options);
|
|
5184
5191
|
}
|
|
5185
5192
|
//#endregion
|
|
5186
5193
|
//#region src/internal/multicodec.ts
|
package/dist/mod.d.cts
CHANGED
|
@@ -169,6 +169,8 @@ interface GetDocumentLoaderOptions extends DocumentLoaderFactoryOptions {
|
|
|
169
169
|
}
|
|
170
170
|
/**
|
|
171
171
|
* Creates a JSON-LD document loader that utilizes the browser's `fetch` API.
|
|
172
|
+
* At most 20 HTTP redirects and alternate document links are followed in total
|
|
173
|
+
* per call. Revisiting a URL within that chain throws a {@link FetchError}.
|
|
172
174
|
*
|
|
173
175
|
* The created loader preloads the below frequently used contexts by default
|
|
174
176
|
* (unless `options.skipPreloadedContexts` is set to `true`):
|
package/dist/mod.d.ts
CHANGED
|
@@ -168,6 +168,8 @@ interface GetDocumentLoaderOptions extends DocumentLoaderFactoryOptions {
|
|
|
168
168
|
}
|
|
169
169
|
/**
|
|
170
170
|
* Creates a JSON-LD document loader that utilizes the browser's `fetch` API.
|
|
171
|
+
* At most 20 HTTP redirects and alternate document links are followed in total
|
|
172
|
+
* per call. Revisiting a URL within that chain throws a {@link FetchError}.
|
|
171
173
|
*
|
|
172
174
|
* The created loader preloads the below frequently used contexts by default
|
|
173
175
|
* (unless `options.skipPreloadedContexts` is set to `true`):
|
package/dist/mod.js
CHANGED
|
@@ -4455,7 +4455,7 @@ const preloadedContexts = {
|
|
|
4455
4455
|
//#endregion
|
|
4456
4456
|
//#region deno.json
|
|
4457
4457
|
var name = "@fedify/vocab-runtime";
|
|
4458
|
-
var version = "2.0.29
|
|
4458
|
+
var version = "2.0.29";
|
|
4459
4459
|
//#endregion
|
|
4460
4460
|
//#region src/request.ts
|
|
4461
4461
|
/**
|
|
@@ -5083,6 +5083,8 @@ async function getRemoteDocument(url, response, fetch) {
|
|
|
5083
5083
|
}
|
|
5084
5084
|
/**
|
|
5085
5085
|
* Creates a JSON-LD document loader that utilizes the browser's `fetch` API.
|
|
5086
|
+
* At most 20 HTTP redirects and alternate document links are followed in total
|
|
5087
|
+
* per call. Revisiting a URL within that chain throws a {@link FetchError}.
|
|
5086
5088
|
*
|
|
5087
5089
|
* The created loader preloads the below frequently used contexts by default
|
|
5088
5090
|
* (unless `options.skipPreloadedContexts` is set to `true`):
|
|
@@ -5159,7 +5161,12 @@ function getDocumentLoader({ allowPrivateAddress, skipPreloadedContexts, userAge
|
|
|
5159
5161
|
}
|
|
5160
5162
|
return await load(redirectUrl, options, redirected + 1, visited);
|
|
5161
5163
|
}
|
|
5162
|
-
const result = await getRemoteDocument(currentUrl, response,
|
|
5164
|
+
const result = await getRemoteDocument(currentUrl, response, async (alternateUrl) => {
|
|
5165
|
+
options?.signal?.throwIfAborted();
|
|
5166
|
+
if (redirected >= DEFAULT_MAX_REDIRECTION) throw new FetchError(currentUrl, `Too many redirections (${redirected + 1})`);
|
|
5167
|
+
if (visited.has(alternateUrl)) throw new FetchError(currentUrl, `Redirect loop detected: ${alternateUrl}`);
|
|
5168
|
+
return await load(alternateUrl, options, redirected + 1, visited);
|
|
5169
|
+
});
|
|
5163
5170
|
span.setAttribute("docloader.document_url", result.documentUrl);
|
|
5164
5171
|
if (result.contextUrl != null) span.setAttribute("docloader.context_url", result.contextUrl);
|
|
5165
5172
|
return result;
|
|
@@ -5175,7 +5182,7 @@ function getDocumentLoader({ allowPrivateAddress, skipPreloadedContexts, userAge
|
|
|
5175
5182
|
}
|
|
5176
5183
|
});
|
|
5177
5184
|
}
|
|
5178
|
-
return load;
|
|
5185
|
+
return (url, options) => load(url, options);
|
|
5179
5186
|
}
|
|
5180
5187
|
//#endregion
|
|
5181
5188
|
//#region src/internal/multicodec.ts
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
require("./chunk-Do9eywBl.cjs");
|
|
2
|
-
const require_request = require("./request-
|
|
2
|
+
const require_request = require("./request-D9e2fcok.cjs");
|
|
3
3
|
let _logtape_logtape = require("@logtape/logtape");
|
|
4
4
|
//#region src/body.ts
|
|
5
5
|
/** Decoded JSON body limit: 16 MiB. @internal */
|
package/dist/tests/body.test.cjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
require("./chunk-Do9eywBl.cjs");
|
|
2
|
-
const require_body = require("./body-
|
|
2
|
+
const require_body = require("./body-ciFbtONg.cjs");
|
|
3
3
|
let node_assert_strict = require("node:assert/strict");
|
|
4
4
|
let node_test = require("node:test");
|
|
5
5
|
let node_zlib = require("node:zlib");
|
package/dist/tests/body.test.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { r as readBoundedText, t as BodyTooLargeError } from "./body-
|
|
1
|
+
import { r as readBoundedText, t as BodyTooLargeError } from "./body-Dg6PqC7C.mjs";
|
|
2
2
|
import { deepStrictEqual, ok, rejects } from "node:assert/strict";
|
|
3
3
|
import { test } from "node:test";
|
|
4
4
|
import { gunzipSync, gzipSync } from "node:zlib";
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
const require_chunk = require("./chunk-Do9eywBl.cjs");
|
|
2
|
-
const require_request = require("./request-
|
|
3
|
-
const require_body = require("./body-
|
|
2
|
+
const require_request = require("./request-D9e2fcok.cjs");
|
|
3
|
+
const require_body = require("./body-ciFbtONg.cjs");
|
|
4
4
|
const require_link = require("./link-FguCydMA.cjs");
|
|
5
5
|
const require_url = require("./url-Qk1svKZU.cjs");
|
|
6
6
|
let node_test = require("node:test");
|
|
@@ -5752,6 +5752,8 @@ async function getRemoteDocument(url, response, fetch) {
|
|
|
5752
5752
|
}
|
|
5753
5753
|
/**
|
|
5754
5754
|
* Creates a JSON-LD document loader that utilizes the browser's `fetch` API.
|
|
5755
|
+
* At most 20 HTTP redirects and alternate document links are followed in total
|
|
5756
|
+
* per call. Revisiting a URL within that chain throws a {@link FetchError}.
|
|
5755
5757
|
*
|
|
5756
5758
|
* The created loader preloads the below frequently used contexts by default
|
|
5757
5759
|
* (unless `options.skipPreloadedContexts` is set to `true`):
|
|
@@ -5828,7 +5830,12 @@ function getDocumentLoader({ allowPrivateAddress, skipPreloadedContexts, userAge
|
|
|
5828
5830
|
}
|
|
5829
5831
|
return await load(redirectUrl, options, redirected + 1, visited);
|
|
5830
5832
|
}
|
|
5831
|
-
const result = await getRemoteDocument(currentUrl, response,
|
|
5833
|
+
const result = await getRemoteDocument(currentUrl, response, async (alternateUrl) => {
|
|
5834
|
+
options?.signal?.throwIfAborted();
|
|
5835
|
+
if (redirected >= DEFAULT_MAX_REDIRECTION) throw new require_request.FetchError(currentUrl, `Too many redirections (${redirected + 1})`);
|
|
5836
|
+
if (visited.has(alternateUrl)) throw new require_request.FetchError(currentUrl, `Redirect loop detected: ${alternateUrl}`);
|
|
5837
|
+
return await load(alternateUrl, options, redirected + 1, visited);
|
|
5838
|
+
});
|
|
5832
5839
|
span.setAttribute("docloader.document_url", result.documentUrl);
|
|
5833
5840
|
if (result.contextUrl != null) span.setAttribute("docloader.context_url", result.contextUrl);
|
|
5834
5841
|
return result;
|
|
@@ -5844,7 +5851,7 @@ function getDocumentLoader({ allowPrivateAddress, skipPreloadedContexts, userAge
|
|
|
5844
5851
|
}
|
|
5845
5852
|
});
|
|
5846
5853
|
}
|
|
5847
|
-
return load;
|
|
5854
|
+
return (url, options) => load(url, options);
|
|
5848
5855
|
}
|
|
5849
5856
|
//#endregion
|
|
5850
5857
|
//#region src/docloader.test.ts
|
|
@@ -6365,4 +6372,150 @@ function getDocumentLoader({ allowPrivateAddress, skipPreloadedContexts, userAge
|
|
|
6365
6372
|
}
|
|
6366
6373
|
});
|
|
6367
6374
|
});
|
|
6375
|
+
(0, node_test.test)("getDocumentLoader() bounds alternate document chains", async (t) => {
|
|
6376
|
+
const base = "https://example.com/alternate-chain/";
|
|
6377
|
+
const loader = getDocumentLoader({ allowPrivateAddress: true });
|
|
6378
|
+
for (const mode of [
|
|
6379
|
+
"header",
|
|
6380
|
+
"html",
|
|
6381
|
+
"mixed"
|
|
6382
|
+
]) for (const hops of [20, 21]) await t.test(`${mode}: ${hops} hops`, async () => {
|
|
6383
|
+
esm_default.mockGlobal();
|
|
6384
|
+
let requests = 0;
|
|
6385
|
+
esm_default.get(`begin:${base}`, ({ url }) => {
|
|
6386
|
+
requests++;
|
|
6387
|
+
const index = Number(new URL(url).pathname.split("/").at(-1));
|
|
6388
|
+
if (index === hops || requests > 30) return Response.json({ done: true });
|
|
6389
|
+
const next = `${base}${index + 1}`;
|
|
6390
|
+
if (mode === "mixed" && index % 2 === 1) return Response.redirect(next, 302);
|
|
6391
|
+
return alternate(next, mode === "html");
|
|
6392
|
+
});
|
|
6393
|
+
try {
|
|
6394
|
+
if (hops === 20) (0, node_assert.deepStrictEqual)((await loader(`${base}0`)).document, { done: true });
|
|
6395
|
+
else await (0, node_assert.rejects)(loader(`${base}0`), (error) => {
|
|
6396
|
+
(0, node_assert.ok)(error instanceof require_request.FetchError);
|
|
6397
|
+
(0, node_assert.ok)(error.message.includes("Too many redirections (21)"));
|
|
6398
|
+
return true;
|
|
6399
|
+
});
|
|
6400
|
+
(0, node_assert.deepStrictEqual)(requests, 21);
|
|
6401
|
+
} finally {
|
|
6402
|
+
esm_default.hardReset();
|
|
6403
|
+
}
|
|
6404
|
+
});
|
|
6405
|
+
for (const mode of [
|
|
6406
|
+
"header",
|
|
6407
|
+
"html",
|
|
6408
|
+
"alternate-redirect",
|
|
6409
|
+
"redirect-alternate",
|
|
6410
|
+
"redirect-intermediate"
|
|
6411
|
+
]) await t.test(`${mode}: cycle`, async () => {
|
|
6412
|
+
esm_default.mockGlobal();
|
|
6413
|
+
let requests = 0;
|
|
6414
|
+
esm_default.get(`begin:${base}`, ({ url }) => {
|
|
6415
|
+
requests++;
|
|
6416
|
+
if (requests > 30) return Response.json({ stopped: true });
|
|
6417
|
+
const index = Number(new URL(url).pathname.split("/").at(-1));
|
|
6418
|
+
const next = `${base}${mode === "redirect-intermediate" ? index === 0 ? 1 : index === 1 ? 2 : 1 : 1 - index}`;
|
|
6419
|
+
if (mode === "alternate-redirect" && index === 1 || (mode === "redirect-alternate" || mode === "redirect-intermediate") && index === 0) return Response.redirect(next, 302);
|
|
6420
|
+
return alternate(next, mode === "html");
|
|
6421
|
+
});
|
|
6422
|
+
try {
|
|
6423
|
+
await (0, node_assert.rejects)(loader(`${base}0`), (error) => {
|
|
6424
|
+
(0, node_assert.ok)(error instanceof require_request.FetchError);
|
|
6425
|
+
(0, node_assert.ok)(error.message.includes("Redirect loop detected:"));
|
|
6426
|
+
return true;
|
|
6427
|
+
});
|
|
6428
|
+
(0, node_assert.deepStrictEqual)(requests, mode === "redirect-intermediate" ? 3 : 2);
|
|
6429
|
+
} finally {
|
|
6430
|
+
esm_default.hardReset();
|
|
6431
|
+
}
|
|
6432
|
+
});
|
|
6433
|
+
await t.test("alternate followed by 20 redirects shares the limit", async () => {
|
|
6434
|
+
esm_default.mockGlobal();
|
|
6435
|
+
let requests = 0;
|
|
6436
|
+
esm_default.get(`begin:${base}`, ({ url }) => {
|
|
6437
|
+
requests++;
|
|
6438
|
+
const index = Number(new URL(url).pathname.split("/").at(-1));
|
|
6439
|
+
if (index === 21) return Response.json({ done: true });
|
|
6440
|
+
return index === 0 ? alternate(`${base}1`, false) : Response.redirect(`${base}${index + 1}`, 302);
|
|
6441
|
+
});
|
|
6442
|
+
try {
|
|
6443
|
+
await (0, node_assert.rejects)(loader(`${base}0`), (error) => {
|
|
6444
|
+
(0, node_assert.ok)(error instanceof require_request.FetchError);
|
|
6445
|
+
(0, node_assert.ok)(error.message.includes("Too many redirections (21)"));
|
|
6446
|
+
return true;
|
|
6447
|
+
});
|
|
6448
|
+
(0, node_assert.deepStrictEqual)(requests, 21);
|
|
6449
|
+
} finally {
|
|
6450
|
+
esm_default.hardReset();
|
|
6451
|
+
}
|
|
6452
|
+
});
|
|
6453
|
+
await t.test("relative alternate after redirect and isolated calls", async () => {
|
|
6454
|
+
esm_default.mockGlobal();
|
|
6455
|
+
esm_default.get(`${base}start`, Response.redirect(`${base}html`, 302));
|
|
6456
|
+
esm_default.get(`${base}html`, alternate("./document", true));
|
|
6457
|
+
esm_default.get(`${base}document`, Response.json({ done: true }));
|
|
6458
|
+
try {
|
|
6459
|
+
for (let i = 0; i < 2; i++) {
|
|
6460
|
+
const results = await Promise.all([loader(`${base}start`), loader(`${base}start`)]);
|
|
6461
|
+
for (const result of results) {
|
|
6462
|
+
(0, node_assert.deepStrictEqual)(result.document, { done: true });
|
|
6463
|
+
(0, node_assert.deepStrictEqual)(result.documentUrl, `${base}document`);
|
|
6464
|
+
}
|
|
6465
|
+
}
|
|
6466
|
+
} finally {
|
|
6467
|
+
esm_default.hardReset();
|
|
6468
|
+
}
|
|
6469
|
+
});
|
|
6470
|
+
function alternate(next, html) {
|
|
6471
|
+
return html ? new Response(`<link rel="alternate" type="application/activity+json" href="${next}">`, { headers: { "Content-Type": "text/html" } }) : new Response("not JSON", { headers: {
|
|
6472
|
+
"Content-Type": "text/plain",
|
|
6473
|
+
Link: `<${next}>; rel="alternate"; type="application/activity+json"`
|
|
6474
|
+
} });
|
|
6475
|
+
}
|
|
6476
|
+
});
|
|
6477
|
+
(0, node_test.test)("getDocumentLoader() preserves cancellation across alternates", async (t) => {
|
|
6478
|
+
const base = "https://example.com/alternate-abort/";
|
|
6479
|
+
const loader = getDocumentLoader({ allowPrivateAddress: true });
|
|
6480
|
+
for (const html of [false, true]) await t.test(html ? "HTML" : "Link header", async () => {
|
|
6481
|
+
esm_default.mockGlobal();
|
|
6482
|
+
const controller = new AbortController();
|
|
6483
|
+
let requests = 0;
|
|
6484
|
+
esm_default.get(`begin:${base}`, ({ url }) => {
|
|
6485
|
+
requests++;
|
|
6486
|
+
const index = Number(new URL(url).pathname.split("/").at(-1));
|
|
6487
|
+
if (index === 2) return Response.json({ done: true });
|
|
6488
|
+
if (index === 1) controller.abort();
|
|
6489
|
+
const next = `${base}${index + 1}`;
|
|
6490
|
+
return new Response(html ? `<link rel="alternate" type="application/activity+json" href="${next}">` : "not JSON", { headers: html ? { "Content-Type": "text/html" } : {
|
|
6491
|
+
"Content-Type": "text/plain",
|
|
6492
|
+
Link: `<${next}>; rel="alternate"; type="application/activity+json"`
|
|
6493
|
+
} });
|
|
6494
|
+
});
|
|
6495
|
+
try {
|
|
6496
|
+
await (0, node_assert.rejects)(loader(`${base}0`, { signal: controller.signal }), { name: "AbortError" });
|
|
6497
|
+
(0, node_assert.deepStrictEqual)(requests, 2);
|
|
6498
|
+
} finally {
|
|
6499
|
+
esm_default.hardReset();
|
|
6500
|
+
}
|
|
6501
|
+
});
|
|
6502
|
+
});
|
|
6503
|
+
(0, node_test.test)("getDocumentLoader() rejects cancellation before fetching", async () => {
|
|
6504
|
+
esm_default.mockGlobal();
|
|
6505
|
+
let requests = 0;
|
|
6506
|
+
const url = "https://example.com/pre-aborted-alternate";
|
|
6507
|
+
esm_default.get(url, () => {
|
|
6508
|
+
requests++;
|
|
6509
|
+
return Response.json({ done: true });
|
|
6510
|
+
});
|
|
6511
|
+
try {
|
|
6512
|
+
const loader = getDocumentLoader({ allowPrivateAddress: true });
|
|
6513
|
+
const controller = new AbortController();
|
|
6514
|
+
controller.abort();
|
|
6515
|
+
await (0, node_assert.rejects)(loader(url, { signal: controller.signal }), { name: "AbortError" });
|
|
6516
|
+
(0, node_assert.deepStrictEqual)(requests, 0);
|
|
6517
|
+
} finally {
|
|
6518
|
+
esm_default.hardReset();
|
|
6519
|
+
}
|
|
6520
|
+
});
|
|
6368
6521
|
//#endregion
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { a as name, i as logRequest, n as createActivityPubRequest, o as version, t as FetchError } from "./request-
|
|
2
|
-
import { n as MAX_BODY_SIZE, r as readBoundedText } from "./body-
|
|
1
|
+
import { a as name, i as logRequest, n as createActivityPubRequest, o as version, t as FetchError } from "./request-CHvRxsQO.mjs";
|
|
2
|
+
import { n as MAX_BODY_SIZE, r as readBoundedText } from "./body-Dg6PqC7C.mjs";
|
|
3
3
|
import { t as HttpHeaderLink } from "./link-NUUWCdnK.mjs";
|
|
4
4
|
import { o as validatePublicUrl, t as UrlError } from "./url-DzlMS01z.mjs";
|
|
5
5
|
import { test } from "node:test";
|
|
@@ -5770,6 +5770,8 @@ async function getRemoteDocument(url, response, fetch) {
|
|
|
5770
5770
|
}
|
|
5771
5771
|
/**
|
|
5772
5772
|
* Creates a JSON-LD document loader that utilizes the browser's `fetch` API.
|
|
5773
|
+
* At most 20 HTTP redirects and alternate document links are followed in total
|
|
5774
|
+
* per call. Revisiting a URL within that chain throws a {@link FetchError}.
|
|
5773
5775
|
*
|
|
5774
5776
|
* The created loader preloads the below frequently used contexts by default
|
|
5775
5777
|
* (unless `options.skipPreloadedContexts` is set to `true`):
|
|
@@ -5846,7 +5848,12 @@ function getDocumentLoader({ allowPrivateAddress, skipPreloadedContexts, userAge
|
|
|
5846
5848
|
}
|
|
5847
5849
|
return await load(redirectUrl, options, redirected + 1, visited);
|
|
5848
5850
|
}
|
|
5849
|
-
const result = await getRemoteDocument(currentUrl, response,
|
|
5851
|
+
const result = await getRemoteDocument(currentUrl, response, async (alternateUrl) => {
|
|
5852
|
+
options?.signal?.throwIfAborted();
|
|
5853
|
+
if (redirected >= DEFAULT_MAX_REDIRECTION) throw new FetchError(currentUrl, `Too many redirections (${redirected + 1})`);
|
|
5854
|
+
if (visited.has(alternateUrl)) throw new FetchError(currentUrl, `Redirect loop detected: ${alternateUrl}`);
|
|
5855
|
+
return await load(alternateUrl, options, redirected + 1, visited);
|
|
5856
|
+
});
|
|
5850
5857
|
span.setAttribute("docloader.document_url", result.documentUrl);
|
|
5851
5858
|
if (result.contextUrl != null) span.setAttribute("docloader.context_url", result.contextUrl);
|
|
5852
5859
|
return result;
|
|
@@ -5862,7 +5869,7 @@ function getDocumentLoader({ allowPrivateAddress, skipPreloadedContexts, userAge
|
|
|
5862
5869
|
}
|
|
5863
5870
|
});
|
|
5864
5871
|
}
|
|
5865
|
-
return load;
|
|
5872
|
+
return (url, options) => load(url, options);
|
|
5866
5873
|
}
|
|
5867
5874
|
//#endregion
|
|
5868
5875
|
//#region src/docloader.test.ts
|
|
@@ -6383,5 +6390,151 @@ test("getDocumentLoader() logs DNS failures as such", async (t) => {
|
|
|
6383
6390
|
}
|
|
6384
6391
|
});
|
|
6385
6392
|
});
|
|
6393
|
+
test("getDocumentLoader() bounds alternate document chains", async (t) => {
|
|
6394
|
+
const base = "https://example.com/alternate-chain/";
|
|
6395
|
+
const loader = getDocumentLoader({ allowPrivateAddress: true });
|
|
6396
|
+
for (const mode of [
|
|
6397
|
+
"header",
|
|
6398
|
+
"html",
|
|
6399
|
+
"mixed"
|
|
6400
|
+
]) for (const hops of [20, 21]) await t.test(`${mode}: ${hops} hops`, async () => {
|
|
6401
|
+
esm_default.mockGlobal();
|
|
6402
|
+
let requests = 0;
|
|
6403
|
+
esm_default.get(`begin:${base}`, ({ url }) => {
|
|
6404
|
+
requests++;
|
|
6405
|
+
const index = Number(new URL(url).pathname.split("/").at(-1));
|
|
6406
|
+
if (index === hops || requests > 30) return Response.json({ done: true });
|
|
6407
|
+
const next = `${base}${index + 1}`;
|
|
6408
|
+
if (mode === "mixed" && index % 2 === 1) return Response.redirect(next, 302);
|
|
6409
|
+
return alternate(next, mode === "html");
|
|
6410
|
+
});
|
|
6411
|
+
try {
|
|
6412
|
+
if (hops === 20) deepStrictEqual((await loader(`${base}0`)).document, { done: true });
|
|
6413
|
+
else await rejects(loader(`${base}0`), (error) => {
|
|
6414
|
+
ok(error instanceof FetchError);
|
|
6415
|
+
ok(error.message.includes("Too many redirections (21)"));
|
|
6416
|
+
return true;
|
|
6417
|
+
});
|
|
6418
|
+
deepStrictEqual(requests, 21);
|
|
6419
|
+
} finally {
|
|
6420
|
+
esm_default.hardReset();
|
|
6421
|
+
}
|
|
6422
|
+
});
|
|
6423
|
+
for (const mode of [
|
|
6424
|
+
"header",
|
|
6425
|
+
"html",
|
|
6426
|
+
"alternate-redirect",
|
|
6427
|
+
"redirect-alternate",
|
|
6428
|
+
"redirect-intermediate"
|
|
6429
|
+
]) await t.test(`${mode}: cycle`, async () => {
|
|
6430
|
+
esm_default.mockGlobal();
|
|
6431
|
+
let requests = 0;
|
|
6432
|
+
esm_default.get(`begin:${base}`, ({ url }) => {
|
|
6433
|
+
requests++;
|
|
6434
|
+
if (requests > 30) return Response.json({ stopped: true });
|
|
6435
|
+
const index = Number(new URL(url).pathname.split("/").at(-1));
|
|
6436
|
+
const next = `${base}${mode === "redirect-intermediate" ? index === 0 ? 1 : index === 1 ? 2 : 1 : 1 - index}`;
|
|
6437
|
+
if (mode === "alternate-redirect" && index === 1 || (mode === "redirect-alternate" || mode === "redirect-intermediate") && index === 0) return Response.redirect(next, 302);
|
|
6438
|
+
return alternate(next, mode === "html");
|
|
6439
|
+
});
|
|
6440
|
+
try {
|
|
6441
|
+
await rejects(loader(`${base}0`), (error) => {
|
|
6442
|
+
ok(error instanceof FetchError);
|
|
6443
|
+
ok(error.message.includes("Redirect loop detected:"));
|
|
6444
|
+
return true;
|
|
6445
|
+
});
|
|
6446
|
+
deepStrictEqual(requests, mode === "redirect-intermediate" ? 3 : 2);
|
|
6447
|
+
} finally {
|
|
6448
|
+
esm_default.hardReset();
|
|
6449
|
+
}
|
|
6450
|
+
});
|
|
6451
|
+
await t.test("alternate followed by 20 redirects shares the limit", async () => {
|
|
6452
|
+
esm_default.mockGlobal();
|
|
6453
|
+
let requests = 0;
|
|
6454
|
+
esm_default.get(`begin:${base}`, ({ url }) => {
|
|
6455
|
+
requests++;
|
|
6456
|
+
const index = Number(new URL(url).pathname.split("/").at(-1));
|
|
6457
|
+
if (index === 21) return Response.json({ done: true });
|
|
6458
|
+
return index === 0 ? alternate(`${base}1`, false) : Response.redirect(`${base}${index + 1}`, 302);
|
|
6459
|
+
});
|
|
6460
|
+
try {
|
|
6461
|
+
await rejects(loader(`${base}0`), (error) => {
|
|
6462
|
+
ok(error instanceof FetchError);
|
|
6463
|
+
ok(error.message.includes("Too many redirections (21)"));
|
|
6464
|
+
return true;
|
|
6465
|
+
});
|
|
6466
|
+
deepStrictEqual(requests, 21);
|
|
6467
|
+
} finally {
|
|
6468
|
+
esm_default.hardReset();
|
|
6469
|
+
}
|
|
6470
|
+
});
|
|
6471
|
+
await t.test("relative alternate after redirect and isolated calls", async () => {
|
|
6472
|
+
esm_default.mockGlobal();
|
|
6473
|
+
esm_default.get(`${base}start`, Response.redirect(`${base}html`, 302));
|
|
6474
|
+
esm_default.get(`${base}html`, alternate("./document", true));
|
|
6475
|
+
esm_default.get(`${base}document`, Response.json({ done: true }));
|
|
6476
|
+
try {
|
|
6477
|
+
for (let i = 0; i < 2; i++) {
|
|
6478
|
+
const results = await Promise.all([loader(`${base}start`), loader(`${base}start`)]);
|
|
6479
|
+
for (const result of results) {
|
|
6480
|
+
deepStrictEqual(result.document, { done: true });
|
|
6481
|
+
deepStrictEqual(result.documentUrl, `${base}document`);
|
|
6482
|
+
}
|
|
6483
|
+
}
|
|
6484
|
+
} finally {
|
|
6485
|
+
esm_default.hardReset();
|
|
6486
|
+
}
|
|
6487
|
+
});
|
|
6488
|
+
function alternate(next, html) {
|
|
6489
|
+
return html ? new Response(`<link rel="alternate" type="application/activity+json" href="${next}">`, { headers: { "Content-Type": "text/html" } }) : new Response("not JSON", { headers: {
|
|
6490
|
+
"Content-Type": "text/plain",
|
|
6491
|
+
Link: `<${next}>; rel="alternate"; type="application/activity+json"`
|
|
6492
|
+
} });
|
|
6493
|
+
}
|
|
6494
|
+
});
|
|
6495
|
+
test("getDocumentLoader() preserves cancellation across alternates", async (t) => {
|
|
6496
|
+
const base = "https://example.com/alternate-abort/";
|
|
6497
|
+
const loader = getDocumentLoader({ allowPrivateAddress: true });
|
|
6498
|
+
for (const html of [false, true]) await t.test(html ? "HTML" : "Link header", async () => {
|
|
6499
|
+
esm_default.mockGlobal();
|
|
6500
|
+
const controller = new AbortController();
|
|
6501
|
+
let requests = 0;
|
|
6502
|
+
esm_default.get(`begin:${base}`, ({ url }) => {
|
|
6503
|
+
requests++;
|
|
6504
|
+
const index = Number(new URL(url).pathname.split("/").at(-1));
|
|
6505
|
+
if (index === 2) return Response.json({ done: true });
|
|
6506
|
+
if (index === 1) controller.abort();
|
|
6507
|
+
const next = `${base}${index + 1}`;
|
|
6508
|
+
return new Response(html ? `<link rel="alternate" type="application/activity+json" href="${next}">` : "not JSON", { headers: html ? { "Content-Type": "text/html" } : {
|
|
6509
|
+
"Content-Type": "text/plain",
|
|
6510
|
+
Link: `<${next}>; rel="alternate"; type="application/activity+json"`
|
|
6511
|
+
} });
|
|
6512
|
+
});
|
|
6513
|
+
try {
|
|
6514
|
+
await rejects(loader(`${base}0`, { signal: controller.signal }), { name: "AbortError" });
|
|
6515
|
+
deepStrictEqual(requests, 2);
|
|
6516
|
+
} finally {
|
|
6517
|
+
esm_default.hardReset();
|
|
6518
|
+
}
|
|
6519
|
+
});
|
|
6520
|
+
});
|
|
6521
|
+
test("getDocumentLoader() rejects cancellation before fetching", async () => {
|
|
6522
|
+
esm_default.mockGlobal();
|
|
6523
|
+
let requests = 0;
|
|
6524
|
+
const url = "https://example.com/pre-aborted-alternate";
|
|
6525
|
+
esm_default.get(url, () => {
|
|
6526
|
+
requests++;
|
|
6527
|
+
return Response.json({ done: true });
|
|
6528
|
+
});
|
|
6529
|
+
try {
|
|
6530
|
+
const loader = getDocumentLoader({ allowPrivateAddress: true });
|
|
6531
|
+
const controller = new AbortController();
|
|
6532
|
+
controller.abort();
|
|
6533
|
+
await rejects(loader(url, { signal: controller.signal }), { name: "AbortError" });
|
|
6534
|
+
deepStrictEqual(requests, 0);
|
|
6535
|
+
} finally {
|
|
6536
|
+
esm_default.hardReset();
|
|
6537
|
+
}
|
|
6538
|
+
});
|
|
6386
6539
|
//#endregion
|
|
6387
6540
|
export {};
|
|
@@ -3,7 +3,7 @@ let node_process = require("node:process");
|
|
|
3
3
|
node_process = require_chunk.__toESM(node_process);
|
|
4
4
|
//#region deno.json
|
|
5
5
|
var name = "@fedify/vocab-runtime";
|
|
6
|
-
var version = "2.0.29
|
|
6
|
+
var version = "2.0.29";
|
|
7
7
|
//#endregion
|
|
8
8
|
//#region src/request.ts
|
|
9
9
|
/**
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
const require_chunk = require("./chunk-Do9eywBl.cjs");
|
|
2
|
-
const require_request = require("./request-
|
|
2
|
+
const require_request = require("./request-D9e2fcok.cjs");
|
|
3
3
|
let node_test = require("node:test");
|
|
4
4
|
let node_process = require("node:process");
|
|
5
5
|
node_process = require_chunk.__toESM(node_process);
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { o as version, r as getUserAgent } from "./request-
|
|
1
|
+
import { o as version, r as getUserAgent } from "./request-CHvRxsQO.mjs";
|
|
2
2
|
import { test } from "node:test";
|
|
3
3
|
import process from "node:process";
|
|
4
4
|
import { deepStrictEqual } from "node:assert";
|
package/package.json
CHANGED
package/src/docloader.test.ts
CHANGED
|
@@ -724,3 +724,208 @@ test("getDocumentLoader() logs DNS failures as such", async (t) => {
|
|
|
724
724
|
});
|
|
725
725
|
}
|
|
726
726
|
});
|
|
727
|
+
|
|
728
|
+
test("getDocumentLoader() bounds alternate document chains", async (t) => {
|
|
729
|
+
const base = "https://example.com/alternate-chain/";
|
|
730
|
+
const loader = getDocumentLoader({ allowPrivateAddress: true });
|
|
731
|
+
for (const mode of ["header", "html", "mixed"] as const) {
|
|
732
|
+
for (const hops of [20, 21]) {
|
|
733
|
+
await t.test(`${mode}: ${hops} hops`, async () => {
|
|
734
|
+
fetchMock.mockGlobal();
|
|
735
|
+
let requests = 0;
|
|
736
|
+
fetchMock.get(`begin:${base}`, ({ url }) => {
|
|
737
|
+
requests++;
|
|
738
|
+
const index = Number(new URL(url).pathname.split("/").at(-1));
|
|
739
|
+
// Keep the unpatched regression bounded too.
|
|
740
|
+
if (index === hops || requests > 30) {
|
|
741
|
+
return Response.json({ done: true });
|
|
742
|
+
}
|
|
743
|
+
const next = `${base}${index + 1}`;
|
|
744
|
+
if (mode === "mixed" && index % 2 === 1) {
|
|
745
|
+
return Response.redirect(next, 302);
|
|
746
|
+
}
|
|
747
|
+
return alternate(next, mode === "html");
|
|
748
|
+
});
|
|
749
|
+
try {
|
|
750
|
+
if (hops === 20) {
|
|
751
|
+
deepStrictEqual((await loader(`${base}0`)).document, {
|
|
752
|
+
done: true,
|
|
753
|
+
});
|
|
754
|
+
} else {
|
|
755
|
+
await rejects(loader(`${base}0`), (error: unknown) => {
|
|
756
|
+
ok(error instanceof FetchError);
|
|
757
|
+
ok(error.message.includes("Too many redirections (21)"));
|
|
758
|
+
return true;
|
|
759
|
+
});
|
|
760
|
+
}
|
|
761
|
+
deepStrictEqual(requests, 21);
|
|
762
|
+
} finally {
|
|
763
|
+
fetchMock.hardReset();
|
|
764
|
+
}
|
|
765
|
+
});
|
|
766
|
+
}
|
|
767
|
+
}
|
|
768
|
+
|
|
769
|
+
for (
|
|
770
|
+
const mode of [
|
|
771
|
+
"header",
|
|
772
|
+
"html",
|
|
773
|
+
"alternate-redirect",
|
|
774
|
+
"redirect-alternate",
|
|
775
|
+
"redirect-intermediate",
|
|
776
|
+
]
|
|
777
|
+
) {
|
|
778
|
+
await t.test(`${mode}: cycle`, async () => {
|
|
779
|
+
fetchMock.mockGlobal();
|
|
780
|
+
let requests = 0;
|
|
781
|
+
fetchMock.get(`begin:${base}`, ({ url }) => {
|
|
782
|
+
requests++;
|
|
783
|
+
if (requests > 30) return Response.json({ stopped: true });
|
|
784
|
+
const index = Number(new URL(url).pathname.split("/").at(-1));
|
|
785
|
+
const next = `${base}${
|
|
786
|
+
mode === "redirect-intermediate"
|
|
787
|
+
? (index === 0 ? 1 : index === 1 ? 2 : 1)
|
|
788
|
+
: 1 - index
|
|
789
|
+
}`;
|
|
790
|
+
if (
|
|
791
|
+
(mode === "alternate-redirect" && index === 1) ||
|
|
792
|
+
((mode === "redirect-alternate" ||
|
|
793
|
+
mode === "redirect-intermediate") && index === 0)
|
|
794
|
+
) {
|
|
795
|
+
return Response.redirect(next, 302);
|
|
796
|
+
}
|
|
797
|
+
return alternate(next, mode === "html");
|
|
798
|
+
});
|
|
799
|
+
try {
|
|
800
|
+
await rejects(loader(`${base}0`), (error: unknown) => {
|
|
801
|
+
ok(error instanceof FetchError);
|
|
802
|
+
ok(error.message.includes("Redirect loop detected:"));
|
|
803
|
+
return true;
|
|
804
|
+
});
|
|
805
|
+
deepStrictEqual(requests, mode === "redirect-intermediate" ? 3 : 2);
|
|
806
|
+
} finally {
|
|
807
|
+
fetchMock.hardReset();
|
|
808
|
+
}
|
|
809
|
+
});
|
|
810
|
+
}
|
|
811
|
+
|
|
812
|
+
await t.test("alternate followed by 20 redirects shares the limit", async () => {
|
|
813
|
+
fetchMock.mockGlobal();
|
|
814
|
+
let requests = 0;
|
|
815
|
+
fetchMock.get(`begin:${base}`, ({ url }) => {
|
|
816
|
+
requests++;
|
|
817
|
+
const index = Number(new URL(url).pathname.split("/").at(-1));
|
|
818
|
+
if (index === 21) return Response.json({ done: true });
|
|
819
|
+
return index === 0
|
|
820
|
+
? alternate(`${base}1`, false)
|
|
821
|
+
: Response.redirect(`${base}${index + 1}`, 302);
|
|
822
|
+
});
|
|
823
|
+
try {
|
|
824
|
+
await rejects(loader(`${base}0`), (error: unknown) => {
|
|
825
|
+
ok(error instanceof FetchError);
|
|
826
|
+
ok(error.message.includes("Too many redirections (21)"));
|
|
827
|
+
return true;
|
|
828
|
+
});
|
|
829
|
+
deepStrictEqual(requests, 21);
|
|
830
|
+
} finally {
|
|
831
|
+
fetchMock.hardReset();
|
|
832
|
+
}
|
|
833
|
+
});
|
|
834
|
+
|
|
835
|
+
await t.test("relative alternate after redirect and isolated calls", async () => {
|
|
836
|
+
fetchMock.mockGlobal();
|
|
837
|
+
fetchMock.get(`${base}start`, Response.redirect(`${base}html`, 302));
|
|
838
|
+
fetchMock.get(`${base}html`, alternate("./document", true));
|
|
839
|
+
fetchMock.get(`${base}document`, Response.json({ done: true }));
|
|
840
|
+
try {
|
|
841
|
+
for (let i = 0; i < 2; i++) {
|
|
842
|
+
const results = await Promise.all([
|
|
843
|
+
loader(`${base}start`),
|
|
844
|
+
loader(`${base}start`),
|
|
845
|
+
]);
|
|
846
|
+
for (const result of results) {
|
|
847
|
+
deepStrictEqual(result.document, { done: true });
|
|
848
|
+
deepStrictEqual(result.documentUrl, `${base}document`);
|
|
849
|
+
}
|
|
850
|
+
}
|
|
851
|
+
} finally {
|
|
852
|
+
fetchMock.hardReset();
|
|
853
|
+
}
|
|
854
|
+
});
|
|
855
|
+
|
|
856
|
+
function alternate(next: string, html: boolean): Response {
|
|
857
|
+
return html
|
|
858
|
+
? new Response(
|
|
859
|
+
`<link rel="alternate" type="application/activity+json" href="${next}">`,
|
|
860
|
+
{
|
|
861
|
+
headers: { "Content-Type": "text/html" },
|
|
862
|
+
},
|
|
863
|
+
)
|
|
864
|
+
: new Response("not JSON", {
|
|
865
|
+
headers: {
|
|
866
|
+
"Content-Type": "text/plain",
|
|
867
|
+
Link: `<${next}>; rel="alternate"; type="application/activity+json"`,
|
|
868
|
+
},
|
|
869
|
+
});
|
|
870
|
+
}
|
|
871
|
+
});
|
|
872
|
+
|
|
873
|
+
test("getDocumentLoader() preserves cancellation across alternates", async (t) => {
|
|
874
|
+
const base = "https://example.com/alternate-abort/";
|
|
875
|
+
const loader = getDocumentLoader({ allowPrivateAddress: true });
|
|
876
|
+
for (const html of [false, true]) {
|
|
877
|
+
await t.test(html ? "HTML" : "Link header", async () => {
|
|
878
|
+
fetchMock.mockGlobal();
|
|
879
|
+
const controller = new AbortController();
|
|
880
|
+
let requests = 0;
|
|
881
|
+
fetchMock.get(`begin:${base}`, ({ url }) => {
|
|
882
|
+
requests++;
|
|
883
|
+
const index = Number(new URL(url).pathname.split("/").at(-1));
|
|
884
|
+
if (index === 2) return Response.json({ done: true });
|
|
885
|
+
if (index === 1) controller.abort();
|
|
886
|
+
const next = `${base}${index + 1}`;
|
|
887
|
+
return new Response(
|
|
888
|
+
html
|
|
889
|
+
? `<link rel="alternate" type="application/activity+json" href="${next}">`
|
|
890
|
+
: "not JSON",
|
|
891
|
+
{
|
|
892
|
+
headers: html ? { "Content-Type": "text/html" } : {
|
|
893
|
+
"Content-Type": "text/plain",
|
|
894
|
+
Link:
|
|
895
|
+
`<${next}>; rel="alternate"; type="application/activity+json"`,
|
|
896
|
+
},
|
|
897
|
+
},
|
|
898
|
+
);
|
|
899
|
+
});
|
|
900
|
+
try {
|
|
901
|
+
await rejects(loader(`${base}0`, { signal: controller.signal }), {
|
|
902
|
+
name: "AbortError",
|
|
903
|
+
});
|
|
904
|
+
deepStrictEqual(requests, 2);
|
|
905
|
+
} finally {
|
|
906
|
+
fetchMock.hardReset();
|
|
907
|
+
}
|
|
908
|
+
});
|
|
909
|
+
}
|
|
910
|
+
});
|
|
911
|
+
|
|
912
|
+
test("getDocumentLoader() rejects cancellation before fetching", async () => {
|
|
913
|
+
fetchMock.mockGlobal();
|
|
914
|
+
let requests = 0;
|
|
915
|
+
const url = "https://example.com/pre-aborted-alternate";
|
|
916
|
+
fetchMock.get(url, () => {
|
|
917
|
+
requests++;
|
|
918
|
+
return Response.json({ done: true });
|
|
919
|
+
});
|
|
920
|
+
try {
|
|
921
|
+
const loader = getDocumentLoader({ allowPrivateAddress: true });
|
|
922
|
+
const controller = new AbortController();
|
|
923
|
+
controller.abort();
|
|
924
|
+
await rejects(loader(url, { signal: controller.signal }), {
|
|
925
|
+
name: "AbortError",
|
|
926
|
+
});
|
|
927
|
+
deepStrictEqual(requests, 0);
|
|
928
|
+
} finally {
|
|
929
|
+
fetchMock.hardReset();
|
|
930
|
+
}
|
|
931
|
+
});
|
package/src/docloader.ts
CHANGED
|
@@ -266,6 +266,8 @@ export interface GetDocumentLoaderOptions extends DocumentLoaderFactoryOptions {
|
|
|
266
266
|
|
|
267
267
|
/**
|
|
268
268
|
* Creates a JSON-LD document loader that utilizes the browser's `fetch` API.
|
|
269
|
+
* At most 20 HTTP redirects and alternate document links are followed in total
|
|
270
|
+
* per call. Revisiting a URL within that chain throws a {@link FetchError}.
|
|
269
271
|
*
|
|
270
272
|
* The created loader preloads the below frequently used contexts by default
|
|
271
273
|
* (unless `options.skipPreloadedContexts` is set to `true`):
|
|
@@ -384,7 +386,26 @@ export function getDocumentLoader(
|
|
|
384
386
|
return await load(redirectUrl, options, redirected + 1, visited);
|
|
385
387
|
}
|
|
386
388
|
|
|
387
|
-
const result = await getRemoteDocument(
|
|
389
|
+
const result = await getRemoteDocument(
|
|
390
|
+
currentUrl,
|
|
391
|
+
response,
|
|
392
|
+
async (alternateUrl) => {
|
|
393
|
+
options?.signal?.throwIfAborted();
|
|
394
|
+
if (redirected >= DEFAULT_MAX_REDIRECTION) {
|
|
395
|
+
throw new FetchError(
|
|
396
|
+
currentUrl,
|
|
397
|
+
`Too many redirections (${redirected + 1})`,
|
|
398
|
+
);
|
|
399
|
+
}
|
|
400
|
+
if (visited.has(alternateUrl)) {
|
|
401
|
+
throw new FetchError(
|
|
402
|
+
currentUrl,
|
|
403
|
+
`Redirect loop detected: ${alternateUrl}`,
|
|
404
|
+
);
|
|
405
|
+
}
|
|
406
|
+
return await load(alternateUrl, options, redirected + 1, visited);
|
|
407
|
+
},
|
|
408
|
+
);
|
|
388
409
|
span.setAttribute("docloader.document_url", result.documentUrl);
|
|
389
410
|
if (result.contextUrl != null) {
|
|
390
411
|
span.setAttribute("docloader.context_url", result.contextUrl);
|
|
@@ -403,5 +424,5 @@ export function getDocumentLoader(
|
|
|
403
424
|
},
|
|
404
425
|
);
|
|
405
426
|
}
|
|
406
|
-
return load;
|
|
427
|
+
return (url, options) => load(url, options);
|
|
407
428
|
}
|