@fedify/vocab-runtime 2.5.0-dev.2448 → 2.5.0-dev.2494
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 +5 -2
- package/dist/{docloader-BBnBpbsG.d.cts → docloader-DD6binpN.d.cts} +4 -8
- package/dist/{docloader-BBnBpbsG.d.ts → docloader-DD6binpN.d.ts} +4 -8
- package/dist/internal/jsonld-cache.cjs +1 -1
- package/dist/internal/jsonld-cache.d.cts +1 -1
- package/dist/internal/jsonld-cache.d.ts +1 -1
- package/dist/internal/jsonld-cache.js +1 -1
- package/dist/internal/portable-dereference.cjs +1 -1
- package/dist/internal/portable-dereference.d.cts +2 -2
- package/dist/internal/portable-dereference.d.ts +2 -2
- package/dist/internal/portable-dereference.js +1 -1
- package/dist/internal.cjs +6 -0
- package/dist/internal.d.cts +108 -0
- package/dist/internal.d.ts +108 -0
- package/dist/internal.js +3 -0
- package/dist/mod.cjs +104 -38
- package/dist/mod.d.cts +3 -336
- package/dist/mod.d.ts +4 -336
- package/dist/mod.js +104 -38
- package/dist/{portable-BDydjp5H.d.ts → portable-1VSnSJFj.d.ts} +1 -1
- package/dist/{portable-Dfvcg_Wy.d.cts → portable-Cr7NwZwb.d.cts} +1 -1
- package/dist/public-fetch-Dvfvawei.cjs +527 -0
- package/dist/public-fetch-ziZ39L5I.js +510 -0
- package/dist/tests/{body-DP2u2wUX.cjs → body-0Or8U7lA.cjs} +1 -1
- package/dist/tests/{body-oV99TVdA.mjs → body-DfH6VTh9.mjs} +1 -1
- package/dist/tests/body.test.cjs +1 -1
- package/dist/tests/body.test.mjs +1 -1
- package/dist/tests/decimal.test.cjs +4 -4
- package/dist/tests/decimal.test.mjs +4 -4
- package/dist/tests/{docloader-D3mA7aPJ.mjs → docloader-BZvFOza_.mjs} +106 -40
- package/dist/tests/{docloader-CwxZguqN.cjs → docloader-W_RHEh5W.cjs} +111 -39
- package/dist/tests/docloader.test.cjs +531 -1293
- package/dist/tests/docloader.test.mjs +472 -1252
- package/dist/tests/esm-Ds-eHTfY.mjs +1229 -0
- package/dist/tests/esm-Q2tdh5f3.cjs +1215 -0
- package/dist/tests/internal/portable-dereference.test.cjs +3 -3
- package/dist/tests/internal/portable-dereference.test.mjs +3 -3
- package/dist/tests/{jsonld-cache-BEXiiM-V.cjs → jsonld-cache-DKIWKxEX.cjs} +1 -1
- package/dist/tests/{jsonld-cache-DFnWsLuZ.mjs → jsonld-cache-KH40z9_7.mjs} +1 -1
- package/dist/tests/jsonld-cache.test.cjs +2 -2
- package/dist/tests/jsonld-cache.test.mjs +2 -2
- package/dist/tests/node-CbBR-th-.mjs +84 -0
- package/dist/tests/node-Ch29PDXR.cjs +89 -0
- package/dist/tests/pin-grant-CPrpk1l4.cjs +70 -0
- package/dist/tests/pin-grant-H5XhM1Xs.mjs +53 -0
- package/dist/tests/pin-grant.test.cjs +87 -0
- package/dist/tests/pin-grant.test.d.cts +1 -0
- package/dist/tests/pin-grant.test.d.mts +1 -0
- package/dist/tests/pin-grant.test.mjs +88 -0
- package/dist/tests/pinners/bun.test.cjs +166 -0
- package/dist/tests/pinners/bun.test.d.cts +1 -0
- package/dist/tests/pinners/bun.test.d.mts +1 -0
- package/dist/tests/pinners/bun.test.mjs +167 -0
- package/dist/tests/pinners/node.test.cjs +269 -0
- package/dist/tests/pinners/node.test.d.cts +1 -0
- package/dist/tests/pinners/node.test.d.mts +1 -0
- package/dist/tests/pinners/node.test.mjs +268 -0
- package/dist/tests/pinners/pinners.test.cjs +196 -0
- package/dist/tests/pinners/pinners.test.d.cts +1 -0
- package/dist/tests/pinners/pinners.test.d.mts +1 -0
- package/dist/tests/pinners/pinners.test.mjs +197 -0
- package/dist/tests/pinners/testdata/node-child.cjs +135 -0
- package/dist/tests/pinners/testdata/node-child.d.cts +1 -0
- package/dist/tests/pinners/testdata/node-child.d.mts +1 -0
- package/dist/tests/pinners/testdata/node-child.mjs +134 -0
- package/dist/tests/pinners-DLH_npLf.cjs +220 -0
- package/dist/tests/pinners-tGcl49uF.mjs +203 -0
- package/dist/tests/{portable-dereference-DaTBBvCj.cjs → portable-dereference-BUoDzMxk.cjs} +2 -2
- package/dist/tests/{portable-dereference-sGRs_qdT.mjs → portable-dereference-Du2k3ngE.mjs} +2 -2
- package/dist/tests/{portable-media-B-oCeotd.mjs → portable-media-CqCq3wqj.mjs} +3 -3
- package/dist/tests/{portable-media-D7ClSjFO.cjs → portable-media-Ls_EOkMN.cjs} +3 -3
- package/dist/tests/portable-media.test.cjs +1 -1
- package/dist/tests/portable-media.test.mjs +1 -1
- package/dist/tests/portable-workers.test.cjs +2 -2
- package/dist/tests/portable-workers.test.mjs +2 -2
- package/dist/tests/public-fetch-BGj5pzUD.cjs +186 -0
- package/dist/tests/public-fetch-Cxo3Yzdf.mjs +170 -0
- package/dist/tests/public-fetch.test.cjs +1073 -0
- package/dist/tests/public-fetch.test.d.cts +1 -0
- package/dist/tests/public-fetch.test.d.mts +1 -0
- package/dist/tests/public-fetch.test.mjs +1074 -0
- package/dist/tests/{request-BlLYI4x3.mjs → request-Bo8cJJhV.mjs} +2 -2
- package/dist/tests/{request-B77_oZnk.cjs → request-Cb6h3X72.cjs} +2 -2
- package/dist/tests/request.test.cjs +2 -2
- package/dist/tests/request.test.mjs +2 -2
- package/dist/tests/support-Bj44xlQu.mjs +272 -0
- package/dist/tests/support-BrdlbLJk.cjs +363 -0
- package/dist/tests/{url-BNakuZ8k.cjs → url-Bdj-rTcC.cjs} +88 -12
- package/dist/tests/{url-DMxmp7ZG.mjs → url-COj5Q8mb.mjs} +65 -13
- package/dist/tests/url.test.cjs +4 -1
- package/dist/tests/url.test.mjs +4 -1
- package/dist/url-4EE56sNP.d.cts +352 -0
- package/dist/url-4EE56sNP.d.ts +352 -0
- package/dist/{url-DrGTR8yv.cjs → url-DIKZnaJv.cjs} +88 -12
- package/dist/{url-Dzyp-NsC.js → url-mWveR5GA.js} +65 -13
- package/package.json +14 -3
- package/src/docloader.test.ts +696 -48
- package/src/docloader.ts +186 -75
- package/src/internal.ts +13 -0
- package/src/pin-grant.test.ts +118 -0
- package/src/pin-grant.ts +99 -0
- package/src/pinners/bun.test.ts +278 -0
- package/src/pinners/bun.ts +223 -0
- package/src/pinners/mod.ts +59 -0
- package/src/pinners/node.test.ts +415 -0
- package/src/pinners/node.ts +144 -0
- package/src/pinners/pinners.test.ts +316 -0
- package/src/pinners/testdata/node-child.ts +147 -0
- package/src/pinners/testdata/proxy-child.ts +82 -0
- package/src/pinners/testdata/support.ts +343 -0
- package/src/pinners/testdata/tls-cert.pem +12 -0
- package/src/pinners/testdata/tls-child.ts +88 -0
- package/src/pinners/testdata/tls-key.pem +5 -0
- package/src/pinners/types.ts +48 -0
- package/src/public-fetch.test.ts +1286 -0
- package/src/public-fetch.ts +338 -0
- package/src/testdata/public-fetch-deno-worker.ts +74 -0
- package/src/url.test.ts +11 -0
- package/src/url.ts +113 -22
- package/tsdown.config.ts +7 -2
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
import { a as logRequest, n as createActivityPubRequest, o as name, s as version, t as FetchError } from "./request-
|
|
2
|
-
import { n as UrlError
|
|
3
|
-
import { i as readBoundedText, n as MAX_BODY_SIZE, r as readBoundedBytes, t as BodyTooLargeError } from "./body-
|
|
1
|
+
import { a as logRequest, n as createActivityPubRequest, o as name, s as version, t as FetchError } from "./request-Bo8cJJhV.mjs";
|
|
2
|
+
import { n as UrlError } from "./url-COj5Q8mb.mjs";
|
|
3
|
+
import { i as readBoundedText, n as MAX_BODY_SIZE, r as readBoundedBytes, t as BodyTooLargeError } from "./body-DfH6VTh9.mjs";
|
|
4
4
|
import { t as preloadedContexts } from "./contexts-duRA_dDC.mjs";
|
|
5
5
|
import { t as HttpHeaderLink } from "./link-Cevmc87v.mjs";
|
|
6
|
+
import { n as openDirectFetch, r as openPublicFetch } from "./public-fetch-Cxo3Yzdf.mjs";
|
|
6
7
|
import { getLogger } from "@logtape/logtape";
|
|
7
8
|
import { SpanKind, SpanStatusCode, trace } from "@opentelemetry/api";
|
|
8
9
|
//#region src/docloader.ts
|
|
@@ -44,7 +45,7 @@ async function bufferErrorResponse(response, url, signal) {
|
|
|
44
45
|
try {
|
|
45
46
|
await readBoundedBytes(response, MAX_ERROR_BODY_SIZE, url);
|
|
46
47
|
} catch (error) {
|
|
47
|
-
|
|
48
|
+
clone.body?.cancel().catch(() => {});
|
|
48
49
|
if (signal?.aborted) throw error;
|
|
49
50
|
logger.debug("Failed to read the error response body from {url}: {error}", {
|
|
50
51
|
url,
|
|
@@ -76,14 +77,48 @@ async function bufferErrorResponse(response, url, signal) {
|
|
|
76
77
|
* Gets a {@link RemoteDocument} from the given response.
|
|
77
78
|
* @param url The URL of the document to load.
|
|
78
79
|
* @param response The response to get the document from.
|
|
79
|
-
* @param fetch The function to fetch the document.
|
|
80
|
+
* @param fetch The function to fetch the document. It is called with the URL
|
|
81
|
+
* of an alternate document, if any, after the response body has
|
|
82
|
+
* been either read or its cancellation has been started.
|
|
80
83
|
* @param options The options for loading the document.
|
|
81
84
|
* @returns The loaded remote document.
|
|
82
85
|
* @throws {FetchError} If the response is not OK.
|
|
83
86
|
* @internal
|
|
84
87
|
*/
|
|
85
88
|
async function getRemoteDocument(url, response, fetch, options) {
|
|
86
|
-
const
|
|
89
|
+
const result = await parseRemoteDocument(url, response, options);
|
|
90
|
+
if ("alternate" in result) return await fetch(result.alternate, options);
|
|
91
|
+
return result.document;
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Starts cancelling the body of a response without waiting for it to settle.
|
|
95
|
+
* The cancellation of a branch of a tee (e.g., a body cloned by a fetch mock)
|
|
96
|
+
* settles only after the other branch is cancelled too, per the Streams
|
|
97
|
+
* standard, so awaiting it could block forever. Failures are only logged.
|
|
98
|
+
* @param response The response whose body is cancelled.
|
|
99
|
+
* @param url The URL of the response, for logging.
|
|
100
|
+
*/
|
|
101
|
+
function cancelResponseBody(response, url) {
|
|
102
|
+
response.body?.cancel().catch((error) => {
|
|
103
|
+
logger.debug("Failed to cancel the response body from {url}: {error}", {
|
|
104
|
+
url,
|
|
105
|
+
error
|
|
106
|
+
});
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Gets a {@link RemoteDocument} from the given response, or the URL of an
|
|
111
|
+
* alternate document to load instead. When an alternate document is found,
|
|
112
|
+
* the response body has been either read or its cancellation has been started
|
|
113
|
+
* (without waiting for it), so that the alternate document can be loaded after
|
|
114
|
+
* the response's session is closed.
|
|
115
|
+
* @param url The URL of the document to load.
|
|
116
|
+
* @param response The response to get the document from.
|
|
117
|
+
* @returns The loaded remote document, or the alternate document URL.
|
|
118
|
+
* @throws {FetchError} If the response is not OK.
|
|
119
|
+
*/
|
|
120
|
+
async function parseRemoteDocument(url, response, options) {
|
|
121
|
+
const documentUrl = url;
|
|
87
122
|
const docUrl = new URL(documentUrl);
|
|
88
123
|
if (!response.ok) {
|
|
89
124
|
if (options?.suppressError) logger.warn("Failed to fetch document: {status} {url} {headers}", {
|
|
@@ -125,7 +160,8 @@ async function getRemoteDocument(url, response, fetch, options) {
|
|
|
125
160
|
alternateUrl: altUri.href,
|
|
126
161
|
url: documentUrl
|
|
127
162
|
});
|
|
128
|
-
|
|
163
|
+
cancelResponseBody(response, documentUrl);
|
|
164
|
+
return { alternate: altUri.href };
|
|
129
165
|
}
|
|
130
166
|
}
|
|
131
167
|
}
|
|
@@ -161,7 +197,7 @@ async function getRemoteDocument(url, response, fetch, options) {
|
|
|
161
197
|
alternateUrl: attribs.href,
|
|
162
198
|
url: documentUrl
|
|
163
199
|
});
|
|
164
|
-
return
|
|
200
|
+
return { alternate: new URL(attribs.href, docUrl).href };
|
|
165
201
|
}
|
|
166
202
|
}
|
|
167
203
|
try {
|
|
@@ -176,11 +212,11 @@ async function getRemoteDocument(url, response, fetch, options) {
|
|
|
176
212
|
url: documentUrl,
|
|
177
213
|
headers: Object.fromEntries(response.headers.entries())
|
|
178
214
|
});
|
|
179
|
-
return {
|
|
215
|
+
return { document: {
|
|
180
216
|
contextUrl,
|
|
181
217
|
document,
|
|
182
218
|
documentUrl
|
|
183
|
-
};
|
|
219
|
+
} };
|
|
184
220
|
}
|
|
185
221
|
/**
|
|
186
222
|
* Resolves {@link DocumentLoaderFactoryOptions.timeout} into milliseconds.
|
|
@@ -285,10 +321,59 @@ function withDocumentLoaderTimeout(loader, timeout) {
|
|
|
285
321
|
* @returns The document loader.
|
|
286
322
|
* @since 1.3.0
|
|
287
323
|
*/
|
|
288
|
-
function getDocumentLoader(
|
|
324
|
+
function getDocumentLoader(options = {}) {
|
|
325
|
+
return createDocumentLoader(options);
|
|
326
|
+
}
|
|
327
|
+
/**
|
|
328
|
+
* The implementation of {@link getDocumentLoader}, with injectable
|
|
329
|
+
* dependencies. This is not exported from any entry point.
|
|
330
|
+
* @param options Options for the document loader.
|
|
331
|
+
* @param deps The dependencies.
|
|
332
|
+
* @returns The document loader.
|
|
333
|
+
*/
|
|
334
|
+
function createDocumentLoader({ allowPrivateAddress, maxRedirection, skipPreloadedContexts, timeout, userAgent } = {}, deps = {}) {
|
|
289
335
|
const resolvedTimeout = resolveDocumentLoaderTimeout(timeout);
|
|
290
336
|
const tracer = trace.getTracerProvider().getTracer(name, version);
|
|
291
337
|
const maximumRedirection = maxRedirection ?? DEFAULT_MAX_REDIRECTION;
|
|
338
|
+
async function openHop(url, options) {
|
|
339
|
+
try {
|
|
340
|
+
const session = deps.openHop != null ? await deps.openHop(url) : allowPrivateAddress ? await openDirectFetch(url) : await openPublicFetch(url, { signal: options?.signal });
|
|
341
|
+
if (options?.signal?.aborted) {
|
|
342
|
+
await session.close();
|
|
343
|
+
options.signal.throwIfAborted();
|
|
344
|
+
}
|
|
345
|
+
return session;
|
|
346
|
+
} catch (error) {
|
|
347
|
+
if (error instanceof UrlError) if (error.reason === "dns") logger.debug("DNS lookup failed for {url}", {
|
|
348
|
+
url,
|
|
349
|
+
error
|
|
350
|
+
});
|
|
351
|
+
else logger[options?.suppressError ? "warn" : "error"]("Disallowed private URL: {url}", {
|
|
352
|
+
url,
|
|
353
|
+
error
|
|
354
|
+
});
|
|
355
|
+
throw error;
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
async function fetchHop(session, url, span, options) {
|
|
359
|
+
try {
|
|
360
|
+
const request = createActivityPubRequest(url, { userAgent });
|
|
361
|
+
logRequest(logger, request);
|
|
362
|
+
const response = await session.fetch(request, {
|
|
363
|
+
redirect: "manual",
|
|
364
|
+
signal: options?.signal
|
|
365
|
+
});
|
|
366
|
+
span.setAttribute("http.response.status_code", response.status);
|
|
367
|
+
if (response.status >= 300 && response.status < 400 && response.headers.has("Location")) {
|
|
368
|
+
const location = response.headers.get("Location");
|
|
369
|
+
cancelResponseBody(response, url);
|
|
370
|
+
return { location };
|
|
371
|
+
}
|
|
372
|
+
return await parseRemoteDocument(url, response, options);
|
|
373
|
+
} finally {
|
|
374
|
+
await session.close();
|
|
375
|
+
}
|
|
376
|
+
}
|
|
292
377
|
async function load(url, options, redirected = 0, visited = /* @__PURE__ */ new Set()) {
|
|
293
378
|
options?.signal?.throwIfAborted();
|
|
294
379
|
const parsedUrl = new URL(url);
|
|
@@ -303,36 +388,15 @@ function getDocumentLoader({ allowPrivateAddress, maxRedirection, skipPreloadedC
|
|
|
303
388
|
documentUrl: currentUrl
|
|
304
389
|
};
|
|
305
390
|
}
|
|
306
|
-
|
|
307
|
-
try {
|
|
308
|
-
await validatePublicUrl(currentUrl);
|
|
309
|
-
} catch (error) {
|
|
310
|
-
if (error instanceof UrlError) if (error.reason === "dns") logger.debug("DNS lookup failed for {url}", {
|
|
311
|
-
url: currentUrl,
|
|
312
|
-
error
|
|
313
|
-
});
|
|
314
|
-
else logger[options?.suppressError ? "warn" : "error"]("Disallowed private URL: {url}", {
|
|
315
|
-
url: currentUrl,
|
|
316
|
-
error
|
|
317
|
-
});
|
|
318
|
-
throw error;
|
|
319
|
-
}
|
|
320
|
-
options?.signal?.throwIfAborted();
|
|
321
|
-
}
|
|
391
|
+
const session = await openHop(currentUrl, options);
|
|
322
392
|
visited.add(currentUrl);
|
|
323
393
|
return await tracer.startActiveSpan("activitypub.fetch_document", {
|
|
324
394
|
kind: SpanKind.CLIENT,
|
|
325
395
|
attributes: { "url.full": currentUrl }
|
|
326
396
|
}, async (span) => {
|
|
327
397
|
try {
|
|
328
|
-
const
|
|
329
|
-
|
|
330
|
-
const response = await fetch(request, {
|
|
331
|
-
redirect: "manual",
|
|
332
|
-
signal: options?.signal
|
|
333
|
-
});
|
|
334
|
-
span.setAttribute("http.response.status_code", response.status);
|
|
335
|
-
if (response.status >= 300 && response.status < 400 && response.headers.has("Location")) {
|
|
398
|
+
const outcome = await fetchHop(session, currentUrl, span, options);
|
|
399
|
+
if ("location" in outcome) {
|
|
336
400
|
if (redirected >= maximumRedirection) {
|
|
337
401
|
logger[options?.suppressError ? "warn" : "error"]("Too many redirections ({redirections}) while fetching document.", {
|
|
338
402
|
redirections: redirected + 1,
|
|
@@ -340,7 +404,7 @@ function getDocumentLoader({ allowPrivateAddress, maxRedirection, skipPreloadedC
|
|
|
340
404
|
});
|
|
341
405
|
throw new FetchError(currentUrl, `Too many redirections (${redirected + 1})`);
|
|
342
406
|
}
|
|
343
|
-
const redirectUrl = new URL(
|
|
407
|
+
const redirectUrl = new URL(outcome.location, currentUrl).href;
|
|
344
408
|
span.setAttribute("http.redirect.url", redirectUrl);
|
|
345
409
|
if (visited.has(redirectUrl)) {
|
|
346
410
|
logger[options?.suppressError ? "warn" : "error"]("Detected a redirect loop while fetching document: {url} -> {redirectUrl}", {
|
|
@@ -351,12 +415,14 @@ function getDocumentLoader({ allowPrivateAddress, maxRedirection, skipPreloadedC
|
|
|
351
415
|
}
|
|
352
416
|
return await load(redirectUrl, options, redirected + 1, visited);
|
|
353
417
|
}
|
|
354
|
-
|
|
418
|
+
let result;
|
|
419
|
+
if ("alternate" in outcome) {
|
|
420
|
+
const alternateUrl = outcome.alternate;
|
|
355
421
|
options?.signal?.throwIfAborted();
|
|
356
422
|
if (redirected >= DEFAULT_MAX_REDIRECTION) throw new FetchError(currentUrl, `Too many redirections (${redirected + 1})`);
|
|
357
423
|
if (visited.has(alternateUrl)) throw new FetchError(currentUrl, `Redirect loop detected: ${alternateUrl}`);
|
|
358
|
-
|
|
359
|
-
}
|
|
424
|
+
result = await load(alternateUrl, options, redirected + 1, visited);
|
|
425
|
+
} else result = outcome.document;
|
|
360
426
|
span.setAttribute("docloader.document_url", result.documentUrl);
|
|
361
427
|
if (result.contextUrl != null) span.setAttribute("docloader.context_url", result.contextUrl);
|
|
362
428
|
return result;
|
|
@@ -375,4 +441,4 @@ function getDocumentLoader({ allowPrivateAddress, maxRedirection, skipPreloadedC
|
|
|
375
441
|
return withDocumentLoaderTimeout((url, options) => load(url, options), resolvedTimeout);
|
|
376
442
|
}
|
|
377
443
|
//#endregion
|
|
378
|
-
export { withDocumentLoaderTimeout as i,
|
|
444
|
+
export { withDocumentLoaderTimeout as a, resolveDocumentLoaderTimeout as i, getDocumentLoader as n, getRemoteDocument as r, createDocumentLoader as t };
|
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
const require_request = require("./request-
|
|
2
|
-
const require_url = require("./url-
|
|
3
|
-
const require_body = require("./body-
|
|
1
|
+
const require_request = require("./request-Cb6h3X72.cjs");
|
|
2
|
+
const require_url = require("./url-Bdj-rTcC.cjs");
|
|
3
|
+
const require_body = require("./body-0Or8U7lA.cjs");
|
|
4
4
|
const require_contexts = require("./contexts-C9Lj11SC.cjs");
|
|
5
5
|
const require_link = require("./link-DlKm8bEr.cjs");
|
|
6
|
+
const require_public_fetch = require("./public-fetch-BGj5pzUD.cjs");
|
|
6
7
|
let _logtape_logtape = require("@logtape/logtape");
|
|
7
8
|
let _opentelemetry_api = require("@opentelemetry/api");
|
|
8
9
|
//#region src/docloader.ts
|
|
@@ -44,7 +45,7 @@ async function bufferErrorResponse(response, url, signal) {
|
|
|
44
45
|
try {
|
|
45
46
|
await require_body.readBoundedBytes(response, MAX_ERROR_BODY_SIZE, url);
|
|
46
47
|
} catch (error) {
|
|
47
|
-
|
|
48
|
+
clone.body?.cancel().catch(() => {});
|
|
48
49
|
if (signal?.aborted) throw error;
|
|
49
50
|
logger.debug("Failed to read the error response body from {url}: {error}", {
|
|
50
51
|
url,
|
|
@@ -76,14 +77,48 @@ async function bufferErrorResponse(response, url, signal) {
|
|
|
76
77
|
* Gets a {@link RemoteDocument} from the given response.
|
|
77
78
|
* @param url The URL of the document to load.
|
|
78
79
|
* @param response The response to get the document from.
|
|
79
|
-
* @param fetch The function to fetch the document.
|
|
80
|
+
* @param fetch The function to fetch the document. It is called with the URL
|
|
81
|
+
* of an alternate document, if any, after the response body has
|
|
82
|
+
* been either read or its cancellation has been started.
|
|
80
83
|
* @param options The options for loading the document.
|
|
81
84
|
* @returns The loaded remote document.
|
|
82
85
|
* @throws {FetchError} If the response is not OK.
|
|
83
86
|
* @internal
|
|
84
87
|
*/
|
|
85
88
|
async function getRemoteDocument(url, response, fetch, options) {
|
|
86
|
-
const
|
|
89
|
+
const result = await parseRemoteDocument(url, response, options);
|
|
90
|
+
if ("alternate" in result) return await fetch(result.alternate, options);
|
|
91
|
+
return result.document;
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Starts cancelling the body of a response without waiting for it to settle.
|
|
95
|
+
* The cancellation of a branch of a tee (e.g., a body cloned by a fetch mock)
|
|
96
|
+
* settles only after the other branch is cancelled too, per the Streams
|
|
97
|
+
* standard, so awaiting it could block forever. Failures are only logged.
|
|
98
|
+
* @param response The response whose body is cancelled.
|
|
99
|
+
* @param url The URL of the response, for logging.
|
|
100
|
+
*/
|
|
101
|
+
function cancelResponseBody(response, url) {
|
|
102
|
+
response.body?.cancel().catch((error) => {
|
|
103
|
+
logger.debug("Failed to cancel the response body from {url}: {error}", {
|
|
104
|
+
url,
|
|
105
|
+
error
|
|
106
|
+
});
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Gets a {@link RemoteDocument} from the given response, or the URL of an
|
|
111
|
+
* alternate document to load instead. When an alternate document is found,
|
|
112
|
+
* the response body has been either read or its cancellation has been started
|
|
113
|
+
* (without waiting for it), so that the alternate document can be loaded after
|
|
114
|
+
* the response's session is closed.
|
|
115
|
+
* @param url The URL of the document to load.
|
|
116
|
+
* @param response The response to get the document from.
|
|
117
|
+
* @returns The loaded remote document, or the alternate document URL.
|
|
118
|
+
* @throws {FetchError} If the response is not OK.
|
|
119
|
+
*/
|
|
120
|
+
async function parseRemoteDocument(url, response, options) {
|
|
121
|
+
const documentUrl = url;
|
|
87
122
|
const docUrl = new URL(documentUrl);
|
|
88
123
|
if (!response.ok) {
|
|
89
124
|
if (options?.suppressError) logger.warn("Failed to fetch document: {status} {url} {headers}", {
|
|
@@ -125,7 +160,8 @@ async function getRemoteDocument(url, response, fetch, options) {
|
|
|
125
160
|
alternateUrl: altUri.href,
|
|
126
161
|
url: documentUrl
|
|
127
162
|
});
|
|
128
|
-
|
|
163
|
+
cancelResponseBody(response, documentUrl);
|
|
164
|
+
return { alternate: altUri.href };
|
|
129
165
|
}
|
|
130
166
|
}
|
|
131
167
|
}
|
|
@@ -161,7 +197,7 @@ async function getRemoteDocument(url, response, fetch, options) {
|
|
|
161
197
|
alternateUrl: attribs.href,
|
|
162
198
|
url: documentUrl
|
|
163
199
|
});
|
|
164
|
-
return
|
|
200
|
+
return { alternate: new URL(attribs.href, docUrl).href };
|
|
165
201
|
}
|
|
166
202
|
}
|
|
167
203
|
try {
|
|
@@ -176,11 +212,11 @@ async function getRemoteDocument(url, response, fetch, options) {
|
|
|
176
212
|
url: documentUrl,
|
|
177
213
|
headers: Object.fromEntries(response.headers.entries())
|
|
178
214
|
});
|
|
179
|
-
return {
|
|
215
|
+
return { document: {
|
|
180
216
|
contextUrl,
|
|
181
217
|
document,
|
|
182
218
|
documentUrl
|
|
183
|
-
};
|
|
219
|
+
} };
|
|
184
220
|
}
|
|
185
221
|
/**
|
|
186
222
|
* Resolves {@link DocumentLoaderFactoryOptions.timeout} into milliseconds.
|
|
@@ -285,10 +321,59 @@ function withDocumentLoaderTimeout(loader, timeout) {
|
|
|
285
321
|
* @returns The document loader.
|
|
286
322
|
* @since 1.3.0
|
|
287
323
|
*/
|
|
288
|
-
function getDocumentLoader(
|
|
324
|
+
function getDocumentLoader(options = {}) {
|
|
325
|
+
return createDocumentLoader(options);
|
|
326
|
+
}
|
|
327
|
+
/**
|
|
328
|
+
* The implementation of {@link getDocumentLoader}, with injectable
|
|
329
|
+
* dependencies. This is not exported from any entry point.
|
|
330
|
+
* @param options Options for the document loader.
|
|
331
|
+
* @param deps The dependencies.
|
|
332
|
+
* @returns The document loader.
|
|
333
|
+
*/
|
|
334
|
+
function createDocumentLoader({ allowPrivateAddress, maxRedirection, skipPreloadedContexts, timeout, userAgent } = {}, deps = {}) {
|
|
289
335
|
const resolvedTimeout = resolveDocumentLoaderTimeout(timeout);
|
|
290
336
|
const tracer = _opentelemetry_api.trace.getTracerProvider().getTracer(require_request.name, require_request.version);
|
|
291
337
|
const maximumRedirection = maxRedirection ?? DEFAULT_MAX_REDIRECTION;
|
|
338
|
+
async function openHop(url, options) {
|
|
339
|
+
try {
|
|
340
|
+
const session = deps.openHop != null ? await deps.openHop(url) : allowPrivateAddress ? await require_public_fetch.openDirectFetch(url) : await require_public_fetch.openPublicFetch(url, { signal: options?.signal });
|
|
341
|
+
if (options?.signal?.aborted) {
|
|
342
|
+
await session.close();
|
|
343
|
+
options.signal.throwIfAborted();
|
|
344
|
+
}
|
|
345
|
+
return session;
|
|
346
|
+
} catch (error) {
|
|
347
|
+
if (error instanceof require_url.UrlError) if (error.reason === "dns") logger.debug("DNS lookup failed for {url}", {
|
|
348
|
+
url,
|
|
349
|
+
error
|
|
350
|
+
});
|
|
351
|
+
else logger[options?.suppressError ? "warn" : "error"]("Disallowed private URL: {url}", {
|
|
352
|
+
url,
|
|
353
|
+
error
|
|
354
|
+
});
|
|
355
|
+
throw error;
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
async function fetchHop(session, url, span, options) {
|
|
359
|
+
try {
|
|
360
|
+
const request = require_request.createActivityPubRequest(url, { userAgent });
|
|
361
|
+
require_request.logRequest(logger, request);
|
|
362
|
+
const response = await session.fetch(request, {
|
|
363
|
+
redirect: "manual",
|
|
364
|
+
signal: options?.signal
|
|
365
|
+
});
|
|
366
|
+
span.setAttribute("http.response.status_code", response.status);
|
|
367
|
+
if (response.status >= 300 && response.status < 400 && response.headers.has("Location")) {
|
|
368
|
+
const location = response.headers.get("Location");
|
|
369
|
+
cancelResponseBody(response, url);
|
|
370
|
+
return { location };
|
|
371
|
+
}
|
|
372
|
+
return await parseRemoteDocument(url, response, options);
|
|
373
|
+
} finally {
|
|
374
|
+
await session.close();
|
|
375
|
+
}
|
|
376
|
+
}
|
|
292
377
|
async function load(url, options, redirected = 0, visited = /* @__PURE__ */ new Set()) {
|
|
293
378
|
options?.signal?.throwIfAborted();
|
|
294
379
|
const parsedUrl = new URL(url);
|
|
@@ -303,36 +388,15 @@ function getDocumentLoader({ allowPrivateAddress, maxRedirection, skipPreloadedC
|
|
|
303
388
|
documentUrl: currentUrl
|
|
304
389
|
};
|
|
305
390
|
}
|
|
306
|
-
|
|
307
|
-
try {
|
|
308
|
-
await require_url.validatePublicUrl(currentUrl);
|
|
309
|
-
} catch (error) {
|
|
310
|
-
if (error instanceof require_url.UrlError) if (error.reason === "dns") logger.debug("DNS lookup failed for {url}", {
|
|
311
|
-
url: currentUrl,
|
|
312
|
-
error
|
|
313
|
-
});
|
|
314
|
-
else logger[options?.suppressError ? "warn" : "error"]("Disallowed private URL: {url}", {
|
|
315
|
-
url: currentUrl,
|
|
316
|
-
error
|
|
317
|
-
});
|
|
318
|
-
throw error;
|
|
319
|
-
}
|
|
320
|
-
options?.signal?.throwIfAborted();
|
|
321
|
-
}
|
|
391
|
+
const session = await openHop(currentUrl, options);
|
|
322
392
|
visited.add(currentUrl);
|
|
323
393
|
return await tracer.startActiveSpan("activitypub.fetch_document", {
|
|
324
394
|
kind: _opentelemetry_api.SpanKind.CLIENT,
|
|
325
395
|
attributes: { "url.full": currentUrl }
|
|
326
396
|
}, async (span) => {
|
|
327
397
|
try {
|
|
328
|
-
const
|
|
329
|
-
|
|
330
|
-
const response = await fetch(request, {
|
|
331
|
-
redirect: "manual",
|
|
332
|
-
signal: options?.signal
|
|
333
|
-
});
|
|
334
|
-
span.setAttribute("http.response.status_code", response.status);
|
|
335
|
-
if (response.status >= 300 && response.status < 400 && response.headers.has("Location")) {
|
|
398
|
+
const outcome = await fetchHop(session, currentUrl, span, options);
|
|
399
|
+
if ("location" in outcome) {
|
|
336
400
|
if (redirected >= maximumRedirection) {
|
|
337
401
|
logger[options?.suppressError ? "warn" : "error"]("Too many redirections ({redirections}) while fetching document.", {
|
|
338
402
|
redirections: redirected + 1,
|
|
@@ -340,7 +404,7 @@ function getDocumentLoader({ allowPrivateAddress, maxRedirection, skipPreloadedC
|
|
|
340
404
|
});
|
|
341
405
|
throw new require_request.FetchError(currentUrl, `Too many redirections (${redirected + 1})`);
|
|
342
406
|
}
|
|
343
|
-
const redirectUrl = new URL(
|
|
407
|
+
const redirectUrl = new URL(outcome.location, currentUrl).href;
|
|
344
408
|
span.setAttribute("http.redirect.url", redirectUrl);
|
|
345
409
|
if (visited.has(redirectUrl)) {
|
|
346
410
|
logger[options?.suppressError ? "warn" : "error"]("Detected a redirect loop while fetching document: {url} -> {redirectUrl}", {
|
|
@@ -351,12 +415,14 @@ function getDocumentLoader({ allowPrivateAddress, maxRedirection, skipPreloadedC
|
|
|
351
415
|
}
|
|
352
416
|
return await load(redirectUrl, options, redirected + 1, visited);
|
|
353
417
|
}
|
|
354
|
-
|
|
418
|
+
let result;
|
|
419
|
+
if ("alternate" in outcome) {
|
|
420
|
+
const alternateUrl = outcome.alternate;
|
|
355
421
|
options?.signal?.throwIfAborted();
|
|
356
422
|
if (redirected >= DEFAULT_MAX_REDIRECTION) throw new require_request.FetchError(currentUrl, `Too many redirections (${redirected + 1})`);
|
|
357
423
|
if (visited.has(alternateUrl)) throw new require_request.FetchError(currentUrl, `Redirect loop detected: ${alternateUrl}`);
|
|
358
|
-
|
|
359
|
-
}
|
|
424
|
+
result = await load(alternateUrl, options, redirected + 1, visited);
|
|
425
|
+
} else result = outcome.document;
|
|
360
426
|
span.setAttribute("docloader.document_url", result.documentUrl);
|
|
361
427
|
if (result.contextUrl != null) span.setAttribute("docloader.context_url", result.contextUrl);
|
|
362
428
|
return result;
|
|
@@ -375,6 +441,12 @@ function getDocumentLoader({ allowPrivateAddress, maxRedirection, skipPreloadedC
|
|
|
375
441
|
return withDocumentLoaderTimeout((url, options) => load(url, options), resolvedTimeout);
|
|
376
442
|
}
|
|
377
443
|
//#endregion
|
|
444
|
+
Object.defineProperty(exports, "createDocumentLoader", {
|
|
445
|
+
enumerable: true,
|
|
446
|
+
get: function() {
|
|
447
|
+
return createDocumentLoader;
|
|
448
|
+
}
|
|
449
|
+
});
|
|
378
450
|
Object.defineProperty(exports, "getDocumentLoader", {
|
|
379
451
|
enumerable: true,
|
|
380
452
|
get: function() {
|