@fedify/vocab-runtime 2.4.0-dev.1964 → 2.4.0-dev.1995
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 +176 -152
- package/dist/mod.js +176 -152
- package/dist/tests/body-B0an0K5j.cjs +94 -0
- package/dist/tests/body-DTu3lmhg.mjs +77 -0
- package/dist/tests/body.test.cjs +89 -0
- package/dist/tests/body.test.d.cts +1 -0
- package/dist/tests/body.test.d.mts +1 -0
- package/dist/tests/body.test.mjs +90 -0
- package/dist/tests/decimal.test.cjs +2 -2
- package/dist/tests/decimal.test.mjs +2 -2
- package/dist/tests/digest.test.cjs +1 -1
- package/dist/tests/digest.test.mjs +1 -1
- package/dist/tests/{docloader-B3auVdRI.cjs → docloader-BiGP4BSX.cjs} +33 -82
- package/dist/tests/{docloader-BexVAGvr.mjs → docloader-DmFInJaI.mjs} +33 -82
- package/dist/tests/docloader.test.cjs +84 -5
- package/dist/tests/docloader.test.mjs +84 -5
- package/dist/tests/internal/multicodec.test.cjs +1 -1
- package/dist/tests/internal/multicodec.test.mjs +1 -1
- package/dist/tests/jsonld-cache.test.cjs +1 -1
- package/dist/tests/jsonld-cache.test.mjs +1 -1
- package/dist/tests/key.test.cjs +1 -1
- package/dist/tests/key.test.mjs +1 -1
- package/dist/tests/langstr.test.cjs +1 -1
- package/dist/tests/langstr.test.mjs +1 -1
- package/dist/tests/link.test.cjs +1 -1
- package/dist/tests/link.test.mjs +1 -1
- package/dist/tests/multibase/multibase.test.cjs +1 -1
- package/dist/tests/multibase/multibase.test.mjs +1 -1
- package/dist/tests/{request-C8BUtPj4.mjs → request-BPFUWEsX.mjs} +1 -1
- package/dist/tests/{request-CRO9h2AO.cjs → request-CAmmk2G7.cjs} +1 -1
- package/dist/tests/request.test.cjs +2 -2
- package/dist/tests/request.test.mjs +2 -2
- package/dist/tests/temporal.test.cjs +1 -1
- package/dist/tests/temporal.test.mjs +1 -1
- package/dist/tests/url.test.cjs +1 -1
- package/dist/tests/url.test.mjs +1 -1
- package/package.json +1 -1
- package/src/body.test.ts +125 -0
- package/src/body.ts +95 -0
- package/src/docloader.test.ts +128 -3
- package/src/docloader.ts +52 -93
package/deno.json
CHANGED
package/dist/mod.cjs
CHANGED
|
@@ -4521,7 +4521,151 @@ const preloadedContexts = {
|
|
|
4521
4521
|
//#endregion
|
|
4522
4522
|
//#region deno.json
|
|
4523
4523
|
var name = "@fedify/vocab-runtime";
|
|
4524
|
-
var version = "2.4.0-dev.
|
|
4524
|
+
var version = "2.4.0-dev.1995+618fd4e7";
|
|
4525
|
+
//#endregion
|
|
4526
|
+
//#region src/request.ts
|
|
4527
|
+
/**
|
|
4528
|
+
* Error thrown when fetching a JSON-LD document failed.
|
|
4529
|
+
*/
|
|
4530
|
+
var FetchError = class extends Error {
|
|
4531
|
+
/**
|
|
4532
|
+
* The URL that failed to fetch.
|
|
4533
|
+
*/
|
|
4534
|
+
url;
|
|
4535
|
+
/**
|
|
4536
|
+
* The HTTP response that failed, if available.
|
|
4537
|
+
*/
|
|
4538
|
+
response;
|
|
4539
|
+
/**
|
|
4540
|
+
* Constructs a new `FetchError`.
|
|
4541
|
+
*
|
|
4542
|
+
* @param url The URL that failed to fetch.
|
|
4543
|
+
* @param message Error message.
|
|
4544
|
+
* @param response The failed HTTP response, if available.
|
|
4545
|
+
*/
|
|
4546
|
+
constructor(url, message, response) {
|
|
4547
|
+
super(message == null ? url.toString() : `${url}: ${message}`);
|
|
4548
|
+
this.name = "FetchError";
|
|
4549
|
+
this.url = typeof url === "string" ? new URL(url) : url;
|
|
4550
|
+
this.response = response;
|
|
4551
|
+
}
|
|
4552
|
+
};
|
|
4553
|
+
/**
|
|
4554
|
+
* Creates a request for the given URL.
|
|
4555
|
+
* @param url The URL to create the request for.
|
|
4556
|
+
* @param options The options for the request.
|
|
4557
|
+
* @returns The created request.
|
|
4558
|
+
* @internal
|
|
4559
|
+
*/
|
|
4560
|
+
function createActivityPubRequest(url, options = {}) {
|
|
4561
|
+
return new Request(url, {
|
|
4562
|
+
headers: {
|
|
4563
|
+
Accept: "application/activity+json, application/ld+json",
|
|
4564
|
+
"User-Agent": typeof options.userAgent === "string" ? options.userAgent : getUserAgent(options.userAgent)
|
|
4565
|
+
},
|
|
4566
|
+
redirect: "manual"
|
|
4567
|
+
});
|
|
4568
|
+
}
|
|
4569
|
+
/**
|
|
4570
|
+
* Gets the user agent string for the given application and URL.
|
|
4571
|
+
* @param options The options for making the user agent string.
|
|
4572
|
+
* @returns The user agent string.
|
|
4573
|
+
* @since 1.3.0
|
|
4574
|
+
*/
|
|
4575
|
+
function getUserAgent({ software, url } = {}) {
|
|
4576
|
+
const fedify = `Fedify/${version}`;
|
|
4577
|
+
const runtime = globalThis.Deno?.version?.deno != null ? `Deno/${Deno.version.deno}` : globalThis.process?.versions?.bun != null ? `Bun/${node_process.default.versions.bun}` : "navigator" in globalThis && navigator.userAgent === "Cloudflare-Workers" ? navigator.userAgent : globalThis.process?.versions?.node != null ? `Node.js/${node_process.default.versions.node}` : null;
|
|
4578
|
+
const userAgent = software == null ? [fedify] : [software, fedify];
|
|
4579
|
+
if (runtime != null) userAgent.push(runtime);
|
|
4580
|
+
if (url != null) userAgent.push(`+${url.toString()}`);
|
|
4581
|
+
return `${userAgent.shift()} (${userAgent.join("; ")})`;
|
|
4582
|
+
}
|
|
4583
|
+
/**
|
|
4584
|
+
* Logs the request.
|
|
4585
|
+
* @param request The request to log.
|
|
4586
|
+
* @internal
|
|
4587
|
+
*/
|
|
4588
|
+
function logRequest(logger, request) {
|
|
4589
|
+
logger.debug("Fetching document: {method} {url} {headers}", {
|
|
4590
|
+
method: request.method,
|
|
4591
|
+
url: request.url,
|
|
4592
|
+
headers: Object.fromEntries(request.headers.entries())
|
|
4593
|
+
});
|
|
4594
|
+
}
|
|
4595
|
+
//#endregion
|
|
4596
|
+
//#region src/body.ts
|
|
4597
|
+
/** Decoded JSON body limit: 16 MiB. @internal */
|
|
4598
|
+
const MAX_BODY_SIZE = 16 * 1024 * 1024;
|
|
4599
|
+
/** A body exceeded the byte limit. @internal */
|
|
4600
|
+
var BodyTooLargeError = class extends FetchError {
|
|
4601
|
+
/** Creates an error for a body exceeding the given limit. */
|
|
4602
|
+
constructor(url, maxBytes) {
|
|
4603
|
+
super(url, `Body exceeds the limit of ${maxBytes} bytes`);
|
|
4604
|
+
this.name = "BodyTooLargeError";
|
|
4605
|
+
}
|
|
4606
|
+
};
|
|
4607
|
+
/** Validates a finite, positive byte limit. @internal */
|
|
4608
|
+
function validateBodySizeLimit(maxBytes) {
|
|
4609
|
+
if (!Number.isSafeInteger(maxBytes) || maxBytes <= 0) throw new RangeError("The body size limit must be a positive safe integer.");
|
|
4610
|
+
}
|
|
4611
|
+
/**
|
|
4612
|
+
* Reads UTF-8 text while limiting bytes received from the body stream.
|
|
4613
|
+
* Fetch responses are already decompressed, so the stream count is authoritative.
|
|
4614
|
+
* @param message The response or request to read.
|
|
4615
|
+
* @param maxBytes The maximum number of decoded bytes to read.
|
|
4616
|
+
* @param url The URL to include in errors and logs.
|
|
4617
|
+
* @returns The decoded body text.
|
|
4618
|
+
* @throws {BodyTooLargeError} If the body exceeds the limit.
|
|
4619
|
+
* @internal
|
|
4620
|
+
*/
|
|
4621
|
+
async function readBoundedText(message, maxBytes, url) {
|
|
4622
|
+
validateBodySizeLimit(maxBytes);
|
|
4623
|
+
const reader = message.body?.getReader();
|
|
4624
|
+
const tooLarge = () => {
|
|
4625
|
+
(0, _logtape_logtape.getLogger)([
|
|
4626
|
+
"fedify",
|
|
4627
|
+
"runtime",
|
|
4628
|
+
"body"
|
|
4629
|
+
]).warn("Body from {url} exceeds the limit of {maxBytes} bytes.", {
|
|
4630
|
+
url: url.toString(),
|
|
4631
|
+
maxBytes
|
|
4632
|
+
});
|
|
4633
|
+
throw new BodyTooLargeError(url, maxBytes);
|
|
4634
|
+
};
|
|
4635
|
+
try {
|
|
4636
|
+
const length = message.headers.get("Content-Length");
|
|
4637
|
+
const encoding = message.headers.get("Content-Encoding");
|
|
4638
|
+
if ((encoding == null || encoding.toLowerCase() === "identity") && length != null && /^\d+$/.test(length) && Number(length) > maxBytes) tooLarge();
|
|
4639
|
+
if (reader == null) return "";
|
|
4640
|
+
let size = 0;
|
|
4641
|
+
const textReader = new ReadableStream({ async pull(controller) {
|
|
4642
|
+
const { done, value } = await reader.read();
|
|
4643
|
+
if (done) {
|
|
4644
|
+
controller.close();
|
|
4645
|
+
return;
|
|
4646
|
+
}
|
|
4647
|
+
size += value.byteLength;
|
|
4648
|
+
if (size > maxBytes) tooLarge();
|
|
4649
|
+
controller.enqueue(value);
|
|
4650
|
+
} }, { highWaterMark: 0 }).pipeThrough(new TextDecoderStream()).getReader();
|
|
4651
|
+
try {
|
|
4652
|
+
const chunks = [];
|
|
4653
|
+
while (true) {
|
|
4654
|
+
const { done, value } = await textReader.read();
|
|
4655
|
+
if (done) break;
|
|
4656
|
+
chunks.push(value);
|
|
4657
|
+
}
|
|
4658
|
+
return chunks.join("");
|
|
4659
|
+
} finally {
|
|
4660
|
+
textReader.releaseLock();
|
|
4661
|
+
}
|
|
4662
|
+
} catch (error) {
|
|
4663
|
+
if (reader != null) reader.cancel(error).catch(() => {});
|
|
4664
|
+
throw error;
|
|
4665
|
+
} finally {
|
|
4666
|
+
reader?.releaseLock();
|
|
4667
|
+
}
|
|
4668
|
+
}
|
|
4525
4669
|
//#endregion
|
|
4526
4670
|
//#region src/link.ts
|
|
4527
4671
|
const parametersNeedLowerCase = ["rel", "type"];
|
|
@@ -4704,76 +4848,6 @@ var HttpHeaderLink = class HttpHeaderLink {
|
|
|
4704
4848
|
}
|
|
4705
4849
|
};
|
|
4706
4850
|
//#endregion
|
|
4707
|
-
//#region src/request.ts
|
|
4708
|
-
/**
|
|
4709
|
-
* Error thrown when fetching a JSON-LD document failed.
|
|
4710
|
-
*/
|
|
4711
|
-
var FetchError = class extends Error {
|
|
4712
|
-
/**
|
|
4713
|
-
* The URL that failed to fetch.
|
|
4714
|
-
*/
|
|
4715
|
-
url;
|
|
4716
|
-
/**
|
|
4717
|
-
* The HTTP response that failed, if available.
|
|
4718
|
-
*/
|
|
4719
|
-
response;
|
|
4720
|
-
/**
|
|
4721
|
-
* Constructs a new `FetchError`.
|
|
4722
|
-
*
|
|
4723
|
-
* @param url The URL that failed to fetch.
|
|
4724
|
-
* @param message Error message.
|
|
4725
|
-
* @param response The failed HTTP response, if available.
|
|
4726
|
-
*/
|
|
4727
|
-
constructor(url, message, response) {
|
|
4728
|
-
super(message == null ? url.toString() : `${url}: ${message}`);
|
|
4729
|
-
this.name = "FetchError";
|
|
4730
|
-
this.url = typeof url === "string" ? new URL(url) : url;
|
|
4731
|
-
this.response = response;
|
|
4732
|
-
}
|
|
4733
|
-
};
|
|
4734
|
-
/**
|
|
4735
|
-
* Creates a request for the given URL.
|
|
4736
|
-
* @param url The URL to create the request for.
|
|
4737
|
-
* @param options The options for the request.
|
|
4738
|
-
* @returns The created request.
|
|
4739
|
-
* @internal
|
|
4740
|
-
*/
|
|
4741
|
-
function createActivityPubRequest(url, options = {}) {
|
|
4742
|
-
return new Request(url, {
|
|
4743
|
-
headers: {
|
|
4744
|
-
Accept: "application/activity+json, application/ld+json",
|
|
4745
|
-
"User-Agent": typeof options.userAgent === "string" ? options.userAgent : getUserAgent(options.userAgent)
|
|
4746
|
-
},
|
|
4747
|
-
redirect: "manual"
|
|
4748
|
-
});
|
|
4749
|
-
}
|
|
4750
|
-
/**
|
|
4751
|
-
* Gets the user agent string for the given application and URL.
|
|
4752
|
-
* @param options The options for making the user agent string.
|
|
4753
|
-
* @returns The user agent string.
|
|
4754
|
-
* @since 1.3.0
|
|
4755
|
-
*/
|
|
4756
|
-
function getUserAgent({ software, url } = {}) {
|
|
4757
|
-
const fedify = `Fedify/${version}`;
|
|
4758
|
-
const runtime = globalThis.Deno?.version?.deno != null ? `Deno/${Deno.version.deno}` : globalThis.process?.versions?.bun != null ? `Bun/${node_process.default.versions.bun}` : "navigator" in globalThis && navigator.userAgent === "Cloudflare-Workers" ? navigator.userAgent : globalThis.process?.versions?.node != null ? `Node.js/${node_process.default.versions.node}` : null;
|
|
4759
|
-
const userAgent = software == null ? [fedify] : [software, fedify];
|
|
4760
|
-
if (runtime != null) userAgent.push(runtime);
|
|
4761
|
-
if (url != null) userAgent.push(`+${url.toString()}`);
|
|
4762
|
-
return `${userAgent.shift()} (${userAgent.join("; ")})`;
|
|
4763
|
-
}
|
|
4764
|
-
/**
|
|
4765
|
-
* Logs the request.
|
|
4766
|
-
* @param request The request to log.
|
|
4767
|
-
* @internal
|
|
4768
|
-
*/
|
|
4769
|
-
function logRequest(logger, request) {
|
|
4770
|
-
logger.debug("Fetching document: {method} {url} {headers}", {
|
|
4771
|
-
method: request.method,
|
|
4772
|
-
url: request.url,
|
|
4773
|
-
headers: Object.fromEntries(request.headers.entries())
|
|
4774
|
-
});
|
|
4775
|
-
}
|
|
4776
|
-
//#endregion
|
|
4777
4851
|
//#region src/docloader.ts
|
|
4778
4852
|
const logger = (0, _logtape_logtape.getLogger)([
|
|
4779
4853
|
"fedify",
|
|
@@ -4789,58 +4863,6 @@ function createResponseMetadata(response) {
|
|
|
4789
4863
|
statusText: response.statusText
|
|
4790
4864
|
});
|
|
4791
4865
|
}
|
|
4792
|
-
async function cancelResponseBody(response) {
|
|
4793
|
-
if (response.body != null) await response.body.cancel();
|
|
4794
|
-
}
|
|
4795
|
-
async function readBoundedText(response, maxBytes) {
|
|
4796
|
-
const contentLength = response.headers.get("Content-Length");
|
|
4797
|
-
if (contentLength != null) {
|
|
4798
|
-
const size = Number(contentLength);
|
|
4799
|
-
if (size > maxBytes) {
|
|
4800
|
-
await cancelResponseBody(response);
|
|
4801
|
-
return {
|
|
4802
|
-
text: "",
|
|
4803
|
-
size,
|
|
4804
|
-
tooLarge: true
|
|
4805
|
-
};
|
|
4806
|
-
}
|
|
4807
|
-
}
|
|
4808
|
-
if (response.body == null) return {
|
|
4809
|
-
text: "",
|
|
4810
|
-
size: 0,
|
|
4811
|
-
tooLarge: false
|
|
4812
|
-
};
|
|
4813
|
-
const reader = response.body.getReader();
|
|
4814
|
-
const decoder = new TextDecoder();
|
|
4815
|
-
let text = "";
|
|
4816
|
-
let size = 0;
|
|
4817
|
-
try {
|
|
4818
|
-
while (true) {
|
|
4819
|
-
const result = await reader.read();
|
|
4820
|
-
if (result.done) break;
|
|
4821
|
-
const chunkSize = result.value.byteLength;
|
|
4822
|
-
if (size + chunkSize > maxBytes) {
|
|
4823
|
-
size += chunkSize;
|
|
4824
|
-
await reader.cancel();
|
|
4825
|
-
return {
|
|
4826
|
-
text: "",
|
|
4827
|
-
size,
|
|
4828
|
-
tooLarge: true
|
|
4829
|
-
};
|
|
4830
|
-
}
|
|
4831
|
-
size += chunkSize;
|
|
4832
|
-
text += decoder.decode(result.value, { stream: true });
|
|
4833
|
-
}
|
|
4834
|
-
text += decoder.decode();
|
|
4835
|
-
return {
|
|
4836
|
-
text,
|
|
4837
|
-
size,
|
|
4838
|
-
tooLarge: false
|
|
4839
|
-
};
|
|
4840
|
-
} finally {
|
|
4841
|
-
reader.releaseLock();
|
|
4842
|
-
}
|
|
4843
|
-
}
|
|
4844
4866
|
/**
|
|
4845
4867
|
* Gets a {@link RemoteDocument} from the given response.
|
|
4846
4868
|
* @param url The URL of the document to load.
|
|
@@ -4896,42 +4918,44 @@ async function getRemoteDocument(url, response, fetch) {
|
|
|
4896
4918
|
let document;
|
|
4897
4919
|
if (!jsonLd && (contentType === "text/html" || contentType?.startsWith("text/html;") || contentType === "application/xhtml+xml" || contentType?.startsWith("application/xhtml+xml;"))) {
|
|
4898
4920
|
const errorResponse = createResponseMetadata(response);
|
|
4899
|
-
|
|
4900
|
-
|
|
4921
|
+
let html;
|
|
4922
|
+
try {
|
|
4923
|
+
html = await readBoundedText(response, MAX_HTML_SIZE, documentUrl);
|
|
4924
|
+
} catch (error) {
|
|
4925
|
+
if (!(error instanceof BodyTooLargeError)) throw error;
|
|
4901
4926
|
logger.warn("HTML response too large, skipping alternate link discovery: {url}", {
|
|
4902
4927
|
url: documentUrl,
|
|
4903
|
-
|
|
4928
|
+
maxBytes: MAX_HTML_SIZE
|
|
4904
4929
|
});
|
|
4905
4930
|
throw new FetchError(documentUrl, `HTML document is too large to scan for an ActivityPub alternate link (Content-Type: ${contentType})`, errorResponse);
|
|
4906
|
-
}
|
|
4907
|
-
|
|
4908
|
-
|
|
4909
|
-
|
|
4910
|
-
|
|
4911
|
-
|
|
4912
|
-
|
|
4913
|
-
|
|
4914
|
-
|
|
4915
|
-
|
|
4916
|
-
|
|
4917
|
-
|
|
4918
|
-
}
|
|
4919
|
-
if (attribs.rel === "alternate" && "type" in attribs && (attribs.type === "application/activity+json" || attribs.type === "application/ld+json" || attribs.type.startsWith("application/ld+json;")) && "href" in attribs && new URL(attribs.href, docUrl).href !== docUrl.href) {
|
|
4920
|
-
logger.debug("Found alternate document: {alternateUrl} from {url}", {
|
|
4921
|
-
alternateUrl: attribs.href,
|
|
4922
|
-
url: documentUrl
|
|
4923
|
-
});
|
|
4924
|
-
return await fetch(new URL(attribs.href, docUrl).href);
|
|
4925
|
-
}
|
|
4931
|
+
}
|
|
4932
|
+
const tagPattern = /<(a|link)\s+([^>]*?)\s*\/?>/gi;
|
|
4933
|
+
const attrPattern = /([a-z][a-z:_-]*)=(?:"([^"]*)"|'([^']*)'|([^\s>]+))/gi;
|
|
4934
|
+
let tagMatch;
|
|
4935
|
+
while ((tagMatch = tagPattern.exec(html)) !== null) {
|
|
4936
|
+
const tagContent = tagMatch[2];
|
|
4937
|
+
let attrMatch;
|
|
4938
|
+
const attribs = {};
|
|
4939
|
+
attrPattern.lastIndex = 0;
|
|
4940
|
+
while ((attrMatch = attrPattern.exec(tagContent)) !== null) {
|
|
4941
|
+
const key = attrMatch[1].toLowerCase();
|
|
4942
|
+
attribs[key] = attrMatch[2] ?? attrMatch[3] ?? attrMatch[4] ?? "";
|
|
4926
4943
|
}
|
|
4927
|
-
|
|
4928
|
-
document
|
|
4929
|
-
|
|
4930
|
-
|
|
4931
|
-
|
|
4944
|
+
if (attribs.rel === "alternate" && "type" in attribs && (attribs.type === "application/activity+json" || attribs.type === "application/ld+json" || attribs.type.startsWith("application/ld+json;")) && "href" in attribs && new URL(attribs.href, docUrl).href !== docUrl.href) {
|
|
4945
|
+
logger.debug("Found alternate document: {alternateUrl} from {url}", {
|
|
4946
|
+
alternateUrl: attribs.href,
|
|
4947
|
+
url: documentUrl
|
|
4948
|
+
});
|
|
4949
|
+
return await fetch(new URL(attribs.href, docUrl).href);
|
|
4932
4950
|
}
|
|
4933
4951
|
}
|
|
4934
|
-
|
|
4952
|
+
try {
|
|
4953
|
+
document = JSON.parse(html);
|
|
4954
|
+
} catch (error) {
|
|
4955
|
+
if (!(error instanceof SyntaxError)) throw error;
|
|
4956
|
+
throw new FetchError(documentUrl, `HTML document has no ActivityPub alternate link (Content-Type: ${contentType})`, errorResponse);
|
|
4957
|
+
}
|
|
4958
|
+
} else document = JSON.parse(await readBoundedText(response, MAX_BODY_SIZE, documentUrl));
|
|
4935
4959
|
logger.debug("Fetched document: {status} {url} {headers}", {
|
|
4936
4960
|
status: response.status,
|
|
4937
4961
|
url: documentUrl,
|