@fedify/vocab-runtime 2.4.0-dev.2193 → 2.4.0-dev.2212

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fedify/vocab-runtime",
3
- "version": "2.4.0-dev.2193+402149b5",
3
+ "version": "2.4.0-dev.2212+26ce9858",
4
4
  "license": "MIT",
5
5
  "exports": {
6
6
  ".": "./src/mod.ts",
package/dist/mod.cjs CHANGED
@@ -16,7 +16,7 @@ let _multiformats_base_x = require("@multiformats/base-x");
16
16
  _multiformats_base_x = require_rolldown_runtime.__toESM(_multiformats_base_x, 1);
17
17
  //#region deno.json
18
18
  var name = "@fedify/vocab-runtime";
19
- var version = "2.4.0-dev.2193+402149b5";
19
+ var version = "2.4.0-dev.2212+26ce9858";
20
20
  //#endregion
21
21
  //#region src/request.ts
22
22
  /**
@@ -1581,6 +1581,149 @@ LanguageString.prototype[Symbol.for("nodejs.util.inspect.custom")] = function(_d
1581
1581
  return `<${this.locale.baseName}> ${inspect(this.toString(), options)}`;
1582
1582
  };
1583
1583
  //#endregion
1584
+ //#region src/portable-media.ts
1585
+ function resolveTimeout(value, fallback) {
1586
+ if (value === null) return null;
1587
+ const result = value ?? fallback;
1588
+ if (!Number.isFinite(result) || result <= 0 || result > 2147483647) throw new RangeError("A media timeout must be a positive finite number of milliseconds.");
1589
+ return Math.ceil(result);
1590
+ }
1591
+ async function withTimeout(operation, parent, timeout) {
1592
+ parent?.throwIfAborted();
1593
+ const controller = new AbortController();
1594
+ const onParentAbort = () => controller.abort(parent?.reason);
1595
+ parent?.addEventListener("abort", onParentAbort, { once: true });
1596
+ let timer;
1597
+ if (timeout != null) timer = setTimeout(() => controller.abort(new DOMException(`Media retrieval timed out after ${timeout} ms.`, "TimeoutError")), timeout);
1598
+ let onAbort;
1599
+ const aborted = new Promise((_, reject) => {
1600
+ onAbort = () => reject(controller.signal.reason);
1601
+ controller.signal.addEventListener("abort", onAbort, { once: true });
1602
+ });
1603
+ try {
1604
+ return await Promise.race([operation(controller.signal), aborted]);
1605
+ } finally {
1606
+ if (timer != null) clearTimeout(timer);
1607
+ parent?.removeEventListener("abort", onParentAbort);
1608
+ if (onAbort != null) controller.signal.removeEventListener("abort", onAbort);
1609
+ }
1610
+ }
1611
+ function mediaTarget(media) {
1612
+ if (typeof media === "string" || media instanceof URL) return {
1613
+ url: media.toString(),
1614
+ digestMultibase: void 0
1615
+ };
1616
+ const target = media.href ?? media.url;
1617
+ const url = target instanceof URL ? target.href : target != null && typeof target === "object" && "href" in target ? target.href?.href : void 0;
1618
+ if (url == null) throw new TypeError("Portable media has no URL or href.");
1619
+ const linkDigest = target != null && !(target instanceof URL) && typeof target === "object" && "digestMultibase" in target ? target.digestMultibase : void 0;
1620
+ if (media.digestMultibase != null && linkDigest != null) {
1621
+ const outer = parseDigestMultibase(media.digestMultibase).digest;
1622
+ const inner = parseDigestMultibase(linkDigest).digest;
1623
+ if (outer.some((byte, i) => byte !== inner[i])) throw new TypeError("Media and linked resource digests disagree.");
1624
+ }
1625
+ return {
1626
+ url,
1627
+ digestMultibase: media.digestMultibase ?? linkDigest
1628
+ };
1629
+ }
1630
+ async function fetchVerified(url, digestMultibase, options, signal, maxBytes) {
1631
+ const visited = /* @__PURE__ */ new Set();
1632
+ for (let redirects = 0; redirects <= 20; redirects++) {
1633
+ signal.throwIfAborted();
1634
+ if (url.username !== "" || url.password !== "") throw new TypeError("Media URLs must not contain credentials.");
1635
+ if (!options.allowPrivateAddress) await require_url.validatePublicUrl(url.href);
1636
+ else if (url.protocol !== "http:" && url.protocol !== "https:") throw new TypeError("Media URLs must use HTTP(S).");
1637
+ if (visited.has(url.href)) throw new TypeError("Media redirect loop.");
1638
+ visited.add(url.href);
1639
+ const response = await fetch(url, {
1640
+ method: "GET",
1641
+ headers: {
1642
+ Accept: "*/*",
1643
+ "User-Agent": typeof options.userAgent === "string" ? options.userAgent : getUserAgent(options.userAgent)
1644
+ },
1645
+ redirect: "manual",
1646
+ signal
1647
+ });
1648
+ if ([
1649
+ 301,
1650
+ 302,
1651
+ 303,
1652
+ 307,
1653
+ 308
1654
+ ].includes(response.status)) {
1655
+ const location = response.headers.get("Location");
1656
+ response.body?.cancel().catch(() => {});
1657
+ if (location == null || redirects === 20) throw new TypeError("Invalid or excessive media redirects.");
1658
+ url = new URL(location, url);
1659
+ continue;
1660
+ }
1661
+ if (response.status !== 200) {
1662
+ response.body?.cancel().catch(() => {});
1663
+ throw new Error(`Media request failed with HTTP ${response.status}.`);
1664
+ }
1665
+ const bytes = await readBoundedBytes(response, maxBytes, url);
1666
+ signal.throwIfAborted();
1667
+ if (!await verifyDigestMultibase(bytes, digestMultibase)) throw new Error("Portable media digest does not match.");
1668
+ const headers = new Headers();
1669
+ const contentType = response.headers.get("Content-Type");
1670
+ if (contentType != null) headers.set("Content-Type", contentType);
1671
+ return new Response(bytes, { headers });
1672
+ }
1673
+ throw new TypeError("Too many media redirects.");
1674
+ }
1675
+ /**
1676
+ * Fetches a portable object's external media and returns it only after its
1677
+ * SHA-256 `digestMultibase` has been verified. `hl:` resources are tried at
1678
+ * the supplied gateways in order. The result contains verified body bytes;
1679
+ * its `Content-Type` is unverified metadata supplied by the server.
1680
+ *
1681
+ * @param media A Link, Image, Document, or URL with an external resource.
1682
+ * @param options Retrieval, integrity, and network policy options.
1683
+ * @returns A response containing only verified bytes.
1684
+ * @throws {TypeError} If the media reference or digest is invalid.
1685
+ * @throws {Error} If no source supplies a matching bounded resource.
1686
+ * @since 2.4.0
1687
+ */
1688
+ async function fetchPortableMedia(media, options = {}) {
1689
+ const { url, digestMultibase: propertyDigest } = mediaTarget(media);
1690
+ const digestMultibase = propertyDigest ?? options.digestMultibase;
1691
+ if (digestMultibase == null) throw new TypeError("Portable media requires digestMultibase.");
1692
+ const expected = parseDigestMultibase(digestMultibase).digest;
1693
+ const maxBytes = options.maxBytes ?? 16777216;
1694
+ if (!Number.isSafeInteger(maxBytes) || maxBytes <= 0) throw new RangeError("The media size limit must be a positive safe integer.");
1695
+ const timeout = resolveTimeout(options.timeout, 1e4);
1696
+ const gatewayTimeout = resolveTimeout(options.gatewayTimeout, 3e3);
1697
+ let targets;
1698
+ let hashlink = false;
1699
+ if (/^hl:/i.test(url)) {
1700
+ hashlink = true;
1701
+ const hashDigest = parseDigestMultibase(parseHashlink(url).digestMultibase).digest;
1702
+ if (expected.some((byte, i) => byte !== hashDigest[i])) throw new TypeError("Hashlink and digestMultibase disagree.");
1703
+ const gateways = [...new Set([...options.gateways ?? []].map((gateway) => require_url.parseGatewayUrl(gateway.toString()).origin))];
1704
+ if (gateways.length === 0) throw new TypeError("Hashlink media requires at least one gateway.");
1705
+ targets = gateways.map((gateway) => new URL(`/.well-known/apgateway/${url}`, gateway));
1706
+ } else {
1707
+ const target = new URL(url);
1708
+ if (target.protocol !== "http:" && target.protocol !== "https:") throw new TypeError("Portable media URLs must use HTTP(S) or hl:.");
1709
+ targets = [target];
1710
+ }
1711
+ return await withTimeout(async (signal) => {
1712
+ let lastError;
1713
+ for (const target of targets) {
1714
+ signal.throwIfAborted();
1715
+ try {
1716
+ return await withTimeout((attemptSignal) => fetchVerified(target, digestMultibase, options, attemptSignal, maxBytes), signal, hashlink ? gatewayTimeout : null);
1717
+ } catch (error) {
1718
+ signal.throwIfAborted();
1719
+ if (!hashlink) throw error;
1720
+ lastError = error;
1721
+ }
1722
+ }
1723
+ throw new Error("No gateway returned verified portable media.", { cause: lastError });
1724
+ }, options.signal, timeout);
1725
+ }
1726
+ //#endregion
1584
1727
  exports.FetchError = FetchError;
1585
1728
  exports.LanguageString = LanguageString;
1586
1729
  exports.UrlError = require_url.UrlError;
@@ -1597,6 +1740,7 @@ exports.expandIPv6Address = require_url.expandIPv6Address;
1597
1740
  exports.exportDidKey = exportDidKey;
1598
1741
  exports.exportMultibaseKey = exportMultibaseKey;
1599
1742
  exports.exportSpki = exportSpki;
1743
+ exports.fetchPortableMedia = fetchPortableMedia;
1600
1744
  exports.formatIri = require_url.formatIri;
1601
1745
  exports.fromCompatibleEf61Id = require_url.fromCompatibleEf61Id;
1602
1746
  exports.getDocumentLoader = getDocumentLoader;
package/dist/mod.d.cts CHANGED
@@ -312,6 +312,50 @@ declare class LanguageString extends String {
312
312
  constructor(value: string, language: Intl.Locale | string);
313
313
  }
314
314
  //#endregion
315
+ //#region src/portable-media.d.ts
316
+ /** A link or document with an external resource and its integrity digest. */
317
+ interface PortableMedia {
318
+ readonly href?: URL | null;
319
+ readonly url?: URL | {
320
+ readonly href: URL | null;
321
+ readonly digestMultibase?: string | null;
322
+ } | null;
323
+ readonly digestMultibase?: string | null;
324
+ }
325
+ /** Options for {@link fetchPortableMedia}. */
326
+ interface FetchPortableMediaOptions {
327
+ /** Ordered FEP-ef61 gateway origins used for `hl:` resources. */
328
+ readonly gateways?: Iterable<string | URL>;
329
+ /** Expected digest when the first argument is a URL or string. */
330
+ readonly digestMultibase?: string;
331
+ /** Maximum decoded response size in bytes. Defaults to 16 MiB. */
332
+ readonly maxBytes?: number;
333
+ /** Whether private network addresses may be fetched. Defaults to `false`. */
334
+ readonly allowPrivateAddress?: boolean;
335
+ /** The HTTP `User-Agent` value or its components. */
336
+ readonly userAgent?: string | GetUserAgentOptions;
337
+ /** Cancels the entire retrieval and all gateway attempts. */
338
+ readonly signal?: AbortSignal;
339
+ /** Overall timeout in milliseconds. Defaults to 10 seconds; `null` disables it. */
340
+ readonly timeout?: number | null;
341
+ /** Timeout per gateway in milliseconds. Defaults to 3 seconds. */
342
+ readonly gatewayTimeout?: number | null;
343
+ }
344
+ /**
345
+ * Fetches a portable object's external media and returns it only after its
346
+ * SHA-256 `digestMultibase` has been verified. `hl:` resources are tried at
347
+ * the supplied gateways in order. The result contains verified body bytes;
348
+ * its `Content-Type` is unverified metadata supplied by the server.
349
+ *
350
+ * @param media A Link, Image, Document, or URL with an external resource.
351
+ * @param options Retrieval, integrity, and network policy options.
352
+ * @returns A response containing only verified bytes.
353
+ * @throws {TypeError} If the media reference or digest is invalid.
354
+ * @throws {Error} If no source supplies a matching bounded resource.
355
+ * @since 2.4.0
356
+ */
357
+ declare function fetchPortableMedia(media: string | URL | PortableMedia, options?: FetchPortableMediaOptions): Promise<Response>;
358
+ //#endregion
315
359
  //#region src/multibase/types.d.ts
316
360
  type BaseCode = "\x00" | "0" | "7" | "9" | "f" | "F" | "v" | "V" | "t" | "T" | "b" | "B" | "c" | "C" | "h" | "k" | "K" | "z" | "Z" | "m" | "M" | "u" | "U";
317
361
  /**
@@ -571,4 +615,4 @@ declare function isValidPublicIPv4Address(address: string): boolean;
571
615
  declare function isValidPublicIPv6Address(address: string): boolean;
572
616
  declare function expandIPv6Address(address: string): string;
573
617
  //#endregion
574
- export { type AuthenticatedDocumentLoaderFactory, type CreateRequestOptions, type Decimal, type DidKeyVerificationMethod, type DocumentLoader, type DocumentLoaderFactory, type DocumentLoaderFactoryOptions, type DocumentLoaderOptions, FetchError, type GetDocumentLoaderOptions, type GetUserAgentOptions, type Json, LanguageString, type ParsedDigestMultibase, type ParsedHashlink, type PortableObjectReferrer, type PortableObjectVerification, type PortableObjectVerifier, type PortableObjectVerifierOptions, type PropertyPreprocessor, type PropertyPreprocessorContext, type RemoteDocument, UrlError, arePortableUrisEqual, canParseDecimal, canonicalizePortableUri, computeDigestMultibase, createActivityPubRequest, createHashlink, decodeMultibase, encodeMultibase, encodingFromBaseData, expandIPv6Address, exportDidKey, exportMultibaseKey, exportSpki, formatIri, fromCompatibleEf61Id, getDocumentLoader, getFe34Origin, getRemoteDocument, getUserAgent, haveSameFe34Origin, haveSameIriOrigin, importDidKey, importMultibaseKey, importPem, importPkcs1, importSpki, isDecimal, isGatewayUrl, isValidPublicIPv4Address, isValidPublicIPv6Address, logRequest, parseDecimal, parseDidKeyVerificationMethod, parseDigestMultibase, parseGatewayUrl, parseHashlink, parseIri, parseJsonLdId, preloadedContexts, resolveDocumentLoaderTimeout, toCompatibleEf61Id, validatePublicUrl, verifyDigestMultibase, verifyHashlink, withDocumentLoaderTimeout };
618
+ export { type AuthenticatedDocumentLoaderFactory, type CreateRequestOptions, type Decimal, type DidKeyVerificationMethod, type DocumentLoader, type DocumentLoaderFactory, type DocumentLoaderFactoryOptions, type DocumentLoaderOptions, FetchError, type FetchPortableMediaOptions, type GetDocumentLoaderOptions, type GetUserAgentOptions, type Json, LanguageString, type ParsedDigestMultibase, type ParsedHashlink, type PortableMedia, type PortableObjectReferrer, type PortableObjectVerification, type PortableObjectVerifier, type PortableObjectVerifierOptions, type PropertyPreprocessor, type PropertyPreprocessorContext, type RemoteDocument, UrlError, arePortableUrisEqual, canParseDecimal, canonicalizePortableUri, computeDigestMultibase, createActivityPubRequest, createHashlink, decodeMultibase, encodeMultibase, encodingFromBaseData, expandIPv6Address, exportDidKey, exportMultibaseKey, exportSpki, fetchPortableMedia, formatIri, fromCompatibleEf61Id, getDocumentLoader, getFe34Origin, getRemoteDocument, getUserAgent, haveSameFe34Origin, haveSameIriOrigin, importDidKey, importMultibaseKey, importPem, importPkcs1, importSpki, isDecimal, isGatewayUrl, isValidPublicIPv4Address, isValidPublicIPv6Address, logRequest, parseDecimal, parseDidKeyVerificationMethod, parseDigestMultibase, parseGatewayUrl, parseHashlink, parseIri, parseJsonLdId, preloadedContexts, resolveDocumentLoaderTimeout, toCompatibleEf61Id, validatePublicUrl, verifyDigestMultibase, verifyHashlink, withDocumentLoaderTimeout };
package/dist/mod.d.ts CHANGED
@@ -311,6 +311,50 @@ declare class LanguageString extends String {
311
311
  constructor(value: string, language: Intl.Locale | string);
312
312
  }
313
313
  //#endregion
314
+ //#region src/portable-media.d.ts
315
+ /** A link or document with an external resource and its integrity digest. */
316
+ interface PortableMedia {
317
+ readonly href?: URL | null;
318
+ readonly url?: URL | {
319
+ readonly href: URL | null;
320
+ readonly digestMultibase?: string | null;
321
+ } | null;
322
+ readonly digestMultibase?: string | null;
323
+ }
324
+ /** Options for {@link fetchPortableMedia}. */
325
+ interface FetchPortableMediaOptions {
326
+ /** Ordered FEP-ef61 gateway origins used for `hl:` resources. */
327
+ readonly gateways?: Iterable<string | URL>;
328
+ /** Expected digest when the first argument is a URL or string. */
329
+ readonly digestMultibase?: string;
330
+ /** Maximum decoded response size in bytes. Defaults to 16 MiB. */
331
+ readonly maxBytes?: number;
332
+ /** Whether private network addresses may be fetched. Defaults to `false`. */
333
+ readonly allowPrivateAddress?: boolean;
334
+ /** The HTTP `User-Agent` value or its components. */
335
+ readonly userAgent?: string | GetUserAgentOptions;
336
+ /** Cancels the entire retrieval and all gateway attempts. */
337
+ readonly signal?: AbortSignal;
338
+ /** Overall timeout in milliseconds. Defaults to 10 seconds; `null` disables it. */
339
+ readonly timeout?: number | null;
340
+ /** Timeout per gateway in milliseconds. Defaults to 3 seconds. */
341
+ readonly gatewayTimeout?: number | null;
342
+ }
343
+ /**
344
+ * Fetches a portable object's external media and returns it only after its
345
+ * SHA-256 `digestMultibase` has been verified. `hl:` resources are tried at
346
+ * the supplied gateways in order. The result contains verified body bytes;
347
+ * its `Content-Type` is unverified metadata supplied by the server.
348
+ *
349
+ * @param media A Link, Image, Document, or URL with an external resource.
350
+ * @param options Retrieval, integrity, and network policy options.
351
+ * @returns A response containing only verified bytes.
352
+ * @throws {TypeError} If the media reference or digest is invalid.
353
+ * @throws {Error} If no source supplies a matching bounded resource.
354
+ * @since 2.4.0
355
+ */
356
+ declare function fetchPortableMedia(media: string | URL | PortableMedia, options?: FetchPortableMediaOptions): Promise<Response>;
357
+ //#endregion
314
358
  //#region src/multibase/types.d.ts
315
359
  type BaseCode = "\x00" | "0" | "7" | "9" | "f" | "F" | "v" | "V" | "t" | "T" | "b" | "B" | "c" | "C" | "h" | "k" | "K" | "z" | "Z" | "m" | "M" | "u" | "U";
316
360
  /**
@@ -570,4 +614,4 @@ declare function isValidPublicIPv4Address(address: string): boolean;
570
614
  declare function isValidPublicIPv6Address(address: string): boolean;
571
615
  declare function expandIPv6Address(address: string): string;
572
616
  //#endregion
573
- export { type AuthenticatedDocumentLoaderFactory, type CreateRequestOptions, type Decimal, type DidKeyVerificationMethod, type DocumentLoader, type DocumentLoaderFactory, type DocumentLoaderFactoryOptions, type DocumentLoaderOptions, FetchError, type GetDocumentLoaderOptions, type GetUserAgentOptions, type Json, LanguageString, type ParsedDigestMultibase, type ParsedHashlink, type PortableObjectReferrer, type PortableObjectVerification, type PortableObjectVerifier, type PortableObjectVerifierOptions, type PropertyPreprocessor, type PropertyPreprocessorContext, type RemoteDocument, UrlError, arePortableUrisEqual, canParseDecimal, canonicalizePortableUri, computeDigestMultibase, createActivityPubRequest, createHashlink, decodeMultibase, encodeMultibase, encodingFromBaseData, expandIPv6Address, exportDidKey, exportMultibaseKey, exportSpki, formatIri, fromCompatibleEf61Id, getDocumentLoader, getFe34Origin, getRemoteDocument, getUserAgent, haveSameFe34Origin, haveSameIriOrigin, importDidKey, importMultibaseKey, importPem, importPkcs1, importSpki, isDecimal, isGatewayUrl, isValidPublicIPv4Address, isValidPublicIPv6Address, logRequest, parseDecimal, parseDidKeyVerificationMethod, parseDigestMultibase, parseGatewayUrl, parseHashlink, parseIri, parseJsonLdId, preloadedContexts, resolveDocumentLoaderTimeout, toCompatibleEf61Id, validatePublicUrl, verifyDigestMultibase, verifyHashlink, withDocumentLoaderTimeout };
617
+ export { type AuthenticatedDocumentLoaderFactory, type CreateRequestOptions, type Decimal, type DidKeyVerificationMethod, type DocumentLoader, type DocumentLoaderFactory, type DocumentLoaderFactoryOptions, type DocumentLoaderOptions, FetchError, type FetchPortableMediaOptions, type GetDocumentLoaderOptions, type GetUserAgentOptions, type Json, LanguageString, type ParsedDigestMultibase, type ParsedHashlink, type PortableMedia, type PortableObjectReferrer, type PortableObjectVerification, type PortableObjectVerifier, type PortableObjectVerifierOptions, type PropertyPreprocessor, type PropertyPreprocessorContext, type RemoteDocument, UrlError, arePortableUrisEqual, canParseDecimal, canonicalizePortableUri, computeDigestMultibase, createActivityPubRequest, createHashlink, decodeMultibase, encodeMultibase, encodingFromBaseData, expandIPv6Address, exportDidKey, exportMultibaseKey, exportSpki, fetchPortableMedia, formatIri, fromCompatibleEf61Id, getDocumentLoader, getFe34Origin, getRemoteDocument, getUserAgent, haveSameFe34Origin, haveSameIriOrigin, importDidKey, importMultibaseKey, importPem, importPkcs1, importSpki, isDecimal, isGatewayUrl, isValidPublicIPv4Address, isValidPublicIPv6Address, logRequest, parseDecimal, parseDidKeyVerificationMethod, parseDigestMultibase, parseGatewayUrl, parseHashlink, parseIri, parseJsonLdId, preloadedContexts, resolveDocumentLoaderTimeout, toCompatibleEf61Id, validatePublicUrl, verifyDigestMultibase, verifyHashlink, withDocumentLoaderTimeout };
package/dist/mod.js CHANGED
@@ -12,7 +12,7 @@ import { PublicKeyInfo } from "pkijs";
12
12
  import baseX from "@multiformats/base-x";
13
13
  //#region deno.json
14
14
  var name = "@fedify/vocab-runtime";
15
- var version = "2.4.0-dev.2193+402149b5";
15
+ var version = "2.4.0-dev.2212+26ce9858";
16
16
  //#endregion
17
17
  //#region src/request.ts
18
18
  /**
@@ -1577,4 +1577,147 @@ LanguageString.prototype[Symbol.for("nodejs.util.inspect.custom")] = function(_d
1577
1577
  return `<${this.locale.baseName}> ${inspect(this.toString(), options)}`;
1578
1578
  };
1579
1579
  //#endregion
1580
- export { FetchError, LanguageString, UrlError, arePortableUrisEqual, canParseDecimal, canonicalizePortableUri, computeDigestMultibase, createActivityPubRequest, createHashlink, decodeMultibase, encodeMultibase, encodingFromBaseData, expandIPv6Address, exportDidKey, exportMultibaseKey, exportSpki, formatIri, fromCompatibleEf61Id, getDocumentLoader, getFe34Origin, getRemoteDocument, getUserAgent, haveSameFe34Origin, haveSameIriOrigin, importDidKey, importMultibaseKey, importPem, importPkcs1, importSpki, isDecimal, isGatewayUrl, isValidPublicIPv4Address, isValidPublicIPv6Address, logRequest, parseDecimal, parseDidKeyVerificationMethod, parseDigestMultibase, parseGatewayUrl, parseHashlink, parseIri, parseJsonLdId, preloadedContexts, resolveDocumentLoaderTimeout, toCompatibleEf61Id, validatePublicUrl, verifyDigestMultibase, verifyHashlink, withDocumentLoaderTimeout };
1580
+ //#region src/portable-media.ts
1581
+ function resolveTimeout(value, fallback) {
1582
+ if (value === null) return null;
1583
+ const result = value ?? fallback;
1584
+ if (!Number.isFinite(result) || result <= 0 || result > 2147483647) throw new RangeError("A media timeout must be a positive finite number of milliseconds.");
1585
+ return Math.ceil(result);
1586
+ }
1587
+ async function withTimeout(operation, parent, timeout) {
1588
+ parent?.throwIfAborted();
1589
+ const controller = new AbortController();
1590
+ const onParentAbort = () => controller.abort(parent?.reason);
1591
+ parent?.addEventListener("abort", onParentAbort, { once: true });
1592
+ let timer;
1593
+ if (timeout != null) timer = setTimeout(() => controller.abort(new DOMException(`Media retrieval timed out after ${timeout} ms.`, "TimeoutError")), timeout);
1594
+ let onAbort;
1595
+ const aborted = new Promise((_, reject) => {
1596
+ onAbort = () => reject(controller.signal.reason);
1597
+ controller.signal.addEventListener("abort", onAbort, { once: true });
1598
+ });
1599
+ try {
1600
+ return await Promise.race([operation(controller.signal), aborted]);
1601
+ } finally {
1602
+ if (timer != null) clearTimeout(timer);
1603
+ parent?.removeEventListener("abort", onParentAbort);
1604
+ if (onAbort != null) controller.signal.removeEventListener("abort", onAbort);
1605
+ }
1606
+ }
1607
+ function mediaTarget(media) {
1608
+ if (typeof media === "string" || media instanceof URL) return {
1609
+ url: media.toString(),
1610
+ digestMultibase: void 0
1611
+ };
1612
+ const target = media.href ?? media.url;
1613
+ const url = target instanceof URL ? target.href : target != null && typeof target === "object" && "href" in target ? target.href?.href : void 0;
1614
+ if (url == null) throw new TypeError("Portable media has no URL or href.");
1615
+ const linkDigest = target != null && !(target instanceof URL) && typeof target === "object" && "digestMultibase" in target ? target.digestMultibase : void 0;
1616
+ if (media.digestMultibase != null && linkDigest != null) {
1617
+ const outer = parseDigestMultibase(media.digestMultibase).digest;
1618
+ const inner = parseDigestMultibase(linkDigest).digest;
1619
+ if (outer.some((byte, i) => byte !== inner[i])) throw new TypeError("Media and linked resource digests disagree.");
1620
+ }
1621
+ return {
1622
+ url,
1623
+ digestMultibase: media.digestMultibase ?? linkDigest
1624
+ };
1625
+ }
1626
+ async function fetchVerified(url, digestMultibase, options, signal, maxBytes) {
1627
+ const visited = /* @__PURE__ */ new Set();
1628
+ for (let redirects = 0; redirects <= 20; redirects++) {
1629
+ signal.throwIfAborted();
1630
+ if (url.username !== "" || url.password !== "") throw new TypeError("Media URLs must not contain credentials.");
1631
+ if (!options.allowPrivateAddress) await validatePublicUrl(url.href);
1632
+ else if (url.protocol !== "http:" && url.protocol !== "https:") throw new TypeError("Media URLs must use HTTP(S).");
1633
+ if (visited.has(url.href)) throw new TypeError("Media redirect loop.");
1634
+ visited.add(url.href);
1635
+ const response = await fetch(url, {
1636
+ method: "GET",
1637
+ headers: {
1638
+ Accept: "*/*",
1639
+ "User-Agent": typeof options.userAgent === "string" ? options.userAgent : getUserAgent(options.userAgent)
1640
+ },
1641
+ redirect: "manual",
1642
+ signal
1643
+ });
1644
+ if ([
1645
+ 301,
1646
+ 302,
1647
+ 303,
1648
+ 307,
1649
+ 308
1650
+ ].includes(response.status)) {
1651
+ const location = response.headers.get("Location");
1652
+ response.body?.cancel().catch(() => {});
1653
+ if (location == null || redirects === 20) throw new TypeError("Invalid or excessive media redirects.");
1654
+ url = new URL(location, url);
1655
+ continue;
1656
+ }
1657
+ if (response.status !== 200) {
1658
+ response.body?.cancel().catch(() => {});
1659
+ throw new Error(`Media request failed with HTTP ${response.status}.`);
1660
+ }
1661
+ const bytes = await readBoundedBytes(response, maxBytes, url);
1662
+ signal.throwIfAborted();
1663
+ if (!await verifyDigestMultibase(bytes, digestMultibase)) throw new Error("Portable media digest does not match.");
1664
+ const headers = new Headers();
1665
+ const contentType = response.headers.get("Content-Type");
1666
+ if (contentType != null) headers.set("Content-Type", contentType);
1667
+ return new Response(bytes, { headers });
1668
+ }
1669
+ throw new TypeError("Too many media redirects.");
1670
+ }
1671
+ /**
1672
+ * Fetches a portable object's external media and returns it only after its
1673
+ * SHA-256 `digestMultibase` has been verified. `hl:` resources are tried at
1674
+ * the supplied gateways in order. The result contains verified body bytes;
1675
+ * its `Content-Type` is unverified metadata supplied by the server.
1676
+ *
1677
+ * @param media A Link, Image, Document, or URL with an external resource.
1678
+ * @param options Retrieval, integrity, and network policy options.
1679
+ * @returns A response containing only verified bytes.
1680
+ * @throws {TypeError} If the media reference or digest is invalid.
1681
+ * @throws {Error} If no source supplies a matching bounded resource.
1682
+ * @since 2.4.0
1683
+ */
1684
+ async function fetchPortableMedia(media, options = {}) {
1685
+ const { url, digestMultibase: propertyDigest } = mediaTarget(media);
1686
+ const digestMultibase = propertyDigest ?? options.digestMultibase;
1687
+ if (digestMultibase == null) throw new TypeError("Portable media requires digestMultibase.");
1688
+ const expected = parseDigestMultibase(digestMultibase).digest;
1689
+ const maxBytes = options.maxBytes ?? 16777216;
1690
+ if (!Number.isSafeInteger(maxBytes) || maxBytes <= 0) throw new RangeError("The media size limit must be a positive safe integer.");
1691
+ const timeout = resolveTimeout(options.timeout, 1e4);
1692
+ const gatewayTimeout = resolveTimeout(options.gatewayTimeout, 3e3);
1693
+ let targets;
1694
+ let hashlink = false;
1695
+ if (/^hl:/i.test(url)) {
1696
+ hashlink = true;
1697
+ const hashDigest = parseDigestMultibase(parseHashlink(url).digestMultibase).digest;
1698
+ if (expected.some((byte, i) => byte !== hashDigest[i])) throw new TypeError("Hashlink and digestMultibase disagree.");
1699
+ const gateways = [...new Set([...options.gateways ?? []].map((gateway) => parseGatewayUrl(gateway.toString()).origin))];
1700
+ if (gateways.length === 0) throw new TypeError("Hashlink media requires at least one gateway.");
1701
+ targets = gateways.map((gateway) => new URL(`/.well-known/apgateway/${url}`, gateway));
1702
+ } else {
1703
+ const target = new URL(url);
1704
+ if (target.protocol !== "http:" && target.protocol !== "https:") throw new TypeError("Portable media URLs must use HTTP(S) or hl:.");
1705
+ targets = [target];
1706
+ }
1707
+ return await withTimeout(async (signal) => {
1708
+ let lastError;
1709
+ for (const target of targets) {
1710
+ signal.throwIfAborted();
1711
+ try {
1712
+ return await withTimeout((attemptSignal) => fetchVerified(target, digestMultibase, options, attemptSignal, maxBytes), signal, hashlink ? gatewayTimeout : null);
1713
+ } catch (error) {
1714
+ signal.throwIfAborted();
1715
+ if (!hashlink) throw error;
1716
+ lastError = error;
1717
+ }
1718
+ }
1719
+ throw new Error("No gateway returned verified portable media.", { cause: lastError });
1720
+ }, options.signal, timeout);
1721
+ }
1722
+ //#endregion
1723
+ export { FetchError, LanguageString, UrlError, arePortableUrisEqual, canParseDecimal, canonicalizePortableUri, computeDigestMultibase, createActivityPubRequest, createHashlink, decodeMultibase, encodeMultibase, encodingFromBaseData, expandIPv6Address, exportDidKey, exportMultibaseKey, exportSpki, fetchPortableMedia, formatIri, fromCompatibleEf61Id, getDocumentLoader, getFe34Origin, getRemoteDocument, getUserAgent, haveSameFe34Origin, haveSameIriOrigin, importDidKey, importMultibaseKey, importPem, importPkcs1, importSpki, isDecimal, isGatewayUrl, isValidPublicIPv4Address, isValidPublicIPv6Address, logRequest, parseDecimal, parseDidKeyVerificationMethod, parseDigestMultibase, parseGatewayUrl, parseHashlink, parseIri, parseJsonLdId, preloadedContexts, resolveDocumentLoaderTimeout, toCompatibleEf61Id, validatePublicUrl, verifyDigestMultibase, verifyHashlink, withDocumentLoaderTimeout };
@@ -1,4 +1,4 @@
1
- const require_request = require("./request-C5_3S-un.cjs");
1
+ const require_request = require("./request-jic6JEwl.cjs");
2
2
  let _logtape_logtape = require("@logtape/logtape");
3
3
  //#region src/body.ts
4
4
  /** Decoded JSON body limit: 16 MiB. @internal */
@@ -1,4 +1,4 @@
1
- import { t as FetchError } from "./request-IIntZ5kj.mjs";
1
+ import { t as FetchError } from "./request-CfuNbRJC.mjs";
2
2
  import { getLogger } from "@logtape/logtape";
3
3
  //#region src/body.ts
4
4
  /** Decoded JSON body limit: 16 MiB. @internal */
@@ -1,4 +1,4 @@
1
- const require_body = require("./body-ORlui-Nx.cjs");
1
+ const require_body = require("./body-BG5Ol-IR.cjs");
2
2
  let node_assert_strict = require("node:assert/strict");
3
3
  let node_test = require("node:test");
4
4
  let node_zlib = require("node:zlib");
@@ -1,4 +1,4 @@
1
- import { i as readBoundedText, t as BodyTooLargeError } from "./body-D1z0X88T.mjs";
1
+ import { i as readBoundedText, t as BodyTooLargeError } from "./body-DbGNUM07.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,9 +1,10 @@
1
1
  require("./url-B8xyTwP-.cjs");
2
- require("./docloader-Vc3BC9ad.cjs");
2
+ require("./docloader-BC9UAgDx.cjs");
3
3
  require("./key-C-AYkdJJ.cjs");
4
4
  require("./multibase-B5Mea7Ip.cjs");
5
5
  require("./digest-3FeH2Y-Q.cjs");
6
6
  require("./langstr-C4Fl80ae.cjs");
7
+ require("./portable-media-Cn4uVXaI.cjs");
7
8
  let node_test = require("node:test");
8
9
  let node_assert = require("node:assert");
9
10
  //#region src/decimal.ts
@@ -1,9 +1,10 @@
1
1
  import "./url-DfS-cvwr.mjs";
2
- import "./docloader-CUdtpDuf.mjs";
2
+ import "./docloader-C-c5dtFJ.mjs";
3
3
  import "./key-C2Db_TAJ.mjs";
4
4
  import "./multibase-BPnF_L4e.mjs";
5
5
  import "./digest-COC7xDiQ.mjs";
6
6
  import "./langstr-CQ26J_L7.mjs";
7
+ import "./portable-media-nj7yXhru.mjs";
7
8
  import { test } from "node:test";
8
9
  import { deepStrictEqual, throws } from "node:assert";
9
10
  //#region src/decimal.ts
@@ -1,5 +1,5 @@
1
- const require_request = require("./request-C5_3S-un.cjs");
2
- const require_body = require("./body-ORlui-Nx.cjs");
1
+ const require_request = require("./request-jic6JEwl.cjs");
2
+ const require_body = require("./body-BG5Ol-IR.cjs");
3
3
  const require_contexts = require("./contexts-DizzBjz4.cjs");
4
4
  const require_link = require("./link-DlKm8bEr.cjs");
5
5
  const require_url = require("./url-B8xyTwP-.cjs");
@@ -1,5 +1,5 @@
1
- import { a as name, i as logRequest, n as createActivityPubRequest, o as version, t as FetchError } from "./request-IIntZ5kj.mjs";
2
- import { i as readBoundedText, n as MAX_BODY_SIZE, r as readBoundedBytes, t as BodyTooLargeError } from "./body-D1z0X88T.mjs";
1
+ import { a as name, i as logRequest, n as createActivityPubRequest, o as version, t as FetchError } from "./request-CfuNbRJC.mjs";
2
+ import { i as readBoundedText, n as MAX_BODY_SIZE, r as readBoundedBytes, t as BodyTooLargeError } from "./body-DbGNUM07.mjs";
3
3
  import { t as preloadedContexts } from "./contexts-CIKsin4e.mjs";
4
4
  import { t as HttpHeaderLink } from "./link-Cevmc87v.mjs";
5
5
  import { t as UrlError, v as validatePublicUrl } from "./url-DfS-cvwr.mjs";
@@ -1,8 +1,8 @@
1
1
  const require_rolldown_runtime = require("./rolldown-runtime-emK7D4bc.cjs");
2
- const require_request = require("./request-C5_3S-un.cjs");
2
+ const require_request = require("./request-jic6JEwl.cjs");
3
3
  const require_contexts = require("./contexts-DizzBjz4.cjs");
4
4
  const require_url = require("./url-B8xyTwP-.cjs");
5
- const require_docloader = require("./docloader-Vc3BC9ad.cjs");
5
+ const require_docloader = require("./docloader-BC9UAgDx.cjs");
6
6
  let node_test = require("node:test");
7
7
  let node_zlib = require("node:zlib");
8
8
  let _logtape_logtape = require("@logtape/logtape");
@@ -1,7 +1,7 @@
1
- import { t as FetchError } from "./request-IIntZ5kj.mjs";
1
+ import { t as FetchError } from "./request-CfuNbRJC.mjs";
2
2
  import { n as cid_v1_default, t as preloadedContexts } from "./contexts-CIKsin4e.mjs";
3
3
  import { t as UrlError } from "./url-DfS-cvwr.mjs";
4
- import { i as withDocumentLoaderTimeout, n as getRemoteDocument, r as resolveDocumentLoaderTimeout, t as getDocumentLoader } from "./docloader-CUdtpDuf.mjs";
4
+ import { i as withDocumentLoaderTimeout, n as getRemoteDocument, r as resolveDocumentLoaderTimeout, t as getDocumentLoader } from "./docloader-C-c5dtFJ.mjs";
5
5
  import { test } from "node:test";
6
6
  import { gzipSync } from "node:zlib";
7
7
  import { configure, reset } from "@logtape/logtape";