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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (152) hide show
  1. package/deno.json +4 -2
  2. package/dist/{tests/docloader-Ck8bcBir.mjs → contexts-B09jb9Pi.cjs} +173 -274
  3. package/dist/{tests/docloader-S_gQZ2tt.cjs → contexts-DWrj4AGR.js} +169 -293
  4. package/dist/{docloader-C_dir7Xb.d.ts → docloader-BMHljyYb.d.cts} +68 -2
  5. package/dist/{docloader-D2DTRiyA.d.cts → docloader-BMHljyYb.d.ts} +68 -2
  6. package/dist/internal/jsonld-cache.cjs +70 -1
  7. package/dist/internal/jsonld-cache.d.cts +35 -2
  8. package/dist/internal/jsonld-cache.d.ts +35 -2
  9. package/dist/internal/jsonld-cache.js +67 -2
  10. package/dist/internal/portable-dereference.cjs +721 -0
  11. package/dist/internal/portable-dereference.d.cts +349 -0
  12. package/dist/internal/portable-dereference.d.ts +349 -0
  13. package/dist/internal/portable-dereference.js +702 -0
  14. package/dist/internal/signed-representation.cjs +378 -0
  15. package/dist/internal/signed-representation.d.cts +144 -0
  16. package/dist/internal/signed-representation.d.ts +144 -0
  17. package/dist/internal/signed-representation.js +371 -0
  18. package/dist/jsonld.cjs +2 -2
  19. package/dist/mod.cjs +762 -4529
  20. package/dist/mod.d.cts +427 -9
  21. package/dist/mod.d.ts +427 -10
  22. package/dist/mod.js +742 -4524
  23. package/dist/portable-6Aj-7dZJ.d.cts +160 -0
  24. package/dist/portable-nkTiqH0w.d.ts +160 -0
  25. package/dist/tests/body-DpC9p-iG.mjs +130 -0
  26. package/dist/tests/body-cKLBprYm.cjs +153 -0
  27. package/dist/tests/body.test.cjs +89 -0
  28. package/dist/tests/body.test.d.cts +1 -0
  29. package/dist/tests/body.test.d.mts +1 -0
  30. package/dist/tests/body.test.mjs +90 -0
  31. package/dist/tests/contexts-C9Lj11SC.cjs +4533 -0
  32. package/dist/tests/contexts-duRA_dDC.mjs +4522 -0
  33. package/dist/tests/decimal.test.cjs +9 -7
  34. package/dist/tests/decimal.test.mjs +9 -6
  35. package/dist/tests/digest-3FeH2Y-Q.cjs +176 -0
  36. package/dist/tests/digest-COC7xDiQ.mjs +141 -0
  37. package/dist/tests/digest.test.cjs +102 -0
  38. package/dist/tests/digest.test.d.cts +1 -0
  39. package/dist/tests/digest.test.d.mts +1 -0
  40. package/dist/tests/digest.test.mjs +103 -0
  41. package/dist/tests/docloader-DyZ1JGIl.cjs +398 -0
  42. package/dist/tests/docloader-tUmMeXjU.mjs +375 -0
  43. package/dist/tests/docloader.test.cjs +816 -39
  44. package/dist/tests/docloader.test.mjs +810 -34
  45. package/dist/tests/internal/multicodec.test.cjs +2 -3
  46. package/dist/tests/internal/multicodec.test.mjs +2 -2
  47. package/dist/tests/internal/portable-dereference.test.cjs +172 -0
  48. package/dist/tests/internal/portable-dereference.test.d.cts +1 -0
  49. package/dist/tests/internal/portable-dereference.test.d.mts +1 -0
  50. package/dist/tests/internal/portable-dereference.test.mjs +173 -0
  51. package/dist/tests/jsonld-CmDhHNTx.cjs +12 -0
  52. package/dist/tests/jsonld-DweIJ07b.mjs +5 -0
  53. package/dist/tests/jsonld-cache-BEXiiM-V.cjs +386 -0
  54. package/dist/tests/jsonld-cache-DFnWsLuZ.mjs +339 -0
  55. package/dist/tests/jsonld-cache.test.cjs +102 -298
  56. package/dist/tests/jsonld-cache.test.mjs +95 -289
  57. package/dist/tests/{key-_wXwomh_.cjs → key-C-AYkdJJ.cjs} +11 -4
  58. package/dist/tests/{key-CDGDH_vC.mjs → key-C2Db_TAJ.mjs} +11 -3
  59. package/dist/tests/key.test.cjs +6 -5
  60. package/dist/tests/key.test.mjs +6 -4
  61. package/dist/tests/{langstr-Di5AvKpB.mjs → langstr-DYRmJqFn.mjs} +5 -2
  62. package/dist/tests/{langstr-CbAxaeEZ.cjs → langstr-_PqsL1ae.cjs} +5 -2
  63. package/dist/tests/langstr.test.cjs +11 -4
  64. package/dist/tests/langstr.test.mjs +9 -2
  65. package/dist/tests/langtag-CUppHs_s.mjs +45 -0
  66. package/dist/tests/langtag-Czjvxfzx.cjs +50 -0
  67. package/dist/tests/langtag.test.cjs +45 -0
  68. package/dist/tests/langtag.test.d.cts +1 -0
  69. package/dist/tests/langtag.test.d.mts +1 -0
  70. package/dist/tests/langtag.test.mjs +46 -0
  71. package/dist/tests/link.test.cjs +2 -3
  72. package/dist/tests/link.test.mjs +2 -2
  73. package/dist/tests/multibase/multibase.test.cjs +8 -9
  74. package/dist/tests/multibase/multibase.test.mjs +6 -6
  75. package/dist/tests/{multibase-Bz_UUDtL.cjs → multibase-B5Mea7Ip.cjs} +19 -2
  76. package/dist/tests/{multibase-B4bvakyA.mjs → multibase-BPnF_L4e.mjs} +12 -1
  77. package/dist/tests/portable-dereference-DmTYDy2p.mjs +250 -0
  78. package/dist/tests/portable-dereference-qTLMqe2_.cjs +279 -0
  79. package/dist/tests/portable-media-5p8azal0.cjs +153 -0
  80. package/dist/tests/portable-media-B_lt9MMA.mjs +148 -0
  81. package/dist/tests/portable-media.test.cjs +222 -0
  82. package/dist/tests/portable-media.test.d.cts +1 -0
  83. package/dist/tests/portable-media.test.d.mts +1 -0
  84. package/dist/tests/portable-media.test.mjs +223 -0
  85. package/dist/tests/portable-workers.test.cjs +36 -0
  86. package/dist/tests/portable-workers.test.d.cts +2 -0
  87. package/dist/tests/portable-workers.test.d.mts +2 -0
  88. package/dist/tests/portable-workers.test.mjs +35 -0
  89. package/dist/tests/{request-uk51rkhO.cjs → request-BgjA9wyl.cjs} +10 -4
  90. package/dist/tests/{request-C8CaGwtt.mjs → request-DodZNukP.mjs} +8 -2
  91. package/dist/tests/request.test.cjs +7 -4
  92. package/dist/tests/request.test.mjs +5 -2
  93. package/dist/tests/signed-representation.test.cjs +588 -0
  94. package/dist/tests/signed-representation.test.d.cts +1 -0
  95. package/dist/tests/signed-representation.test.d.mts +1 -0
  96. package/dist/tests/signed-representation.test.mjs +589 -0
  97. package/dist/tests/temporal.test.cjs +1 -2
  98. package/dist/tests/temporal.test.mjs +1 -1
  99. package/dist/tests/url-BNakuZ8k.cjs +998 -0
  100. package/dist/tests/url-DMxmp7ZG.mjs +859 -0
  101. package/dist/tests/url.test.cjs +489 -6
  102. package/dist/tests/url.test.mjs +489 -5
  103. package/dist/url-DrGTR8yv.cjs +993 -0
  104. package/dist/url-Dzyp-NsC.js +860 -0
  105. package/package.json +27 -4
  106. package/scripts/test-bun.mjs +17 -0
  107. package/src/body.test.ts +125 -0
  108. package/src/body.ts +152 -0
  109. package/src/contexts/cid-v1.json +114 -0
  110. package/src/contexts/fep-22cd.json +21 -0
  111. package/src/contexts/fep-6757.json +12 -0
  112. package/src/contexts/joinmastodon.json +1 -1
  113. package/src/contexts/miscellany.json +17 -0
  114. package/src/contexts.ts +47 -1
  115. package/src/digest.test.ts +220 -0
  116. package/src/digest.ts +229 -0
  117. package/src/docloader.test.ts +1066 -27
  118. package/src/docloader.ts +322 -105
  119. package/src/internal/jsonld-cache.ts +97 -1
  120. package/src/internal/portable-dereference.test.ts +254 -0
  121. package/src/internal/portable-dereference.ts +1000 -0
  122. package/src/internal/signed-representation.ts +565 -0
  123. package/src/jsonld-cache.test.ts +89 -0
  124. package/src/key.test.ts +9 -0
  125. package/src/key.ts +11 -1
  126. package/src/langstr.test.ts +18 -1
  127. package/src/langstr.ts +6 -2
  128. package/src/langtag.test.ts +53 -0
  129. package/src/langtag.ts +76 -0
  130. package/src/mod.ts +29 -0
  131. package/src/multibase/multibase.test.ts +5 -5
  132. package/src/portable-media.test.ts +293 -0
  133. package/src/portable-media.ts +264 -0
  134. package/src/portable-workers.test.ts +81 -0
  135. package/src/portable.ts +180 -0
  136. package/src/preprocessor.ts +7 -0
  137. package/src/request.test.ts +10 -1
  138. package/src/request.ts +9 -1
  139. package/src/signed-representation.test.ts +338 -0
  140. package/src/url.test.ts +823 -3
  141. package/src/url.ts +700 -23
  142. package/tsdown.config.ts +2 -0
  143. package/dist/tests/url-CsOV_B_P.cjs +0 -482
  144. package/dist/tests/url-Du7RQQgP.mjs +0 -392
  145. package/dist/url-DD4F0ULf.cjs +0 -483
  146. package/dist/url-DGVbSVVi.js +0 -393
  147. /package/dist/{chunk-M78iaK0I.cjs → rolldown-runtime-B7lfambq.cjs} +0 -0
  148. /package/dist/tests/{link-NUUWCdnK.mjs → link-Cevmc87v.mjs} +0 -0
  149. /package/dist/tests/{link-FguCydMA.cjs → link-DlKm8bEr.cjs} +0 -0
  150. /package/dist/tests/{multicodec-CxGVGa91.cjs → multicodec-CLRPeW4N.cjs} +0 -0
  151. /package/dist/tests/{multicodec-CyFp54fI.mjs → multicodec-CRIj_05H.mjs} +0 -0
  152. /package/dist/tests/{chunk-C2EiDwsr.cjs → rolldown-runtime-emK7D4bc.cjs} +0 -0
@@ -0,0 +1,279 @@
1
+ const require_contexts = require("./contexts-C9Lj11SC.cjs");
2
+ const require_url = require("./url-BNakuZ8k.cjs");
3
+ require("./jsonld-CmDhHNTx.cjs");
4
+ const require_jsonld_cache = require("./jsonld-cache-BEXiiM-V.cjs");
5
+ let _logtape_logtape = require("@logtape/logtape");
6
+ require("@opentelemetry/api");
7
+ //#region src/internal/portable-dereference.ts
8
+ const logger = (0, _logtape_logtape.getLogger)([
9
+ "fedify",
10
+ "vocab",
11
+ "gateway"
12
+ ]);
13
+ /**
14
+ * The maximum number of `@gateway` location hints to try for one reference.
15
+ * Hints come from possibly untrusted documents, so they are bounded to keep
16
+ * a single accessor call from fanning out to many servers.
17
+ */
18
+ const MAX_GATEWAY_HINTS = 5;
19
+ const BASELINE_CONTEXT_URLS = /* @__PURE__ */ new Set([
20
+ "https://w3id.org/identity/v1",
21
+ "https://www.w3.org/ns/activitystreams",
22
+ "https://w3id.org/security/v1",
23
+ "https://w3id.org/security/data-integrity/v1"
24
+ ]);
25
+ const provenances = /* @__PURE__ */ new WeakMap();
26
+ function getObjectId(object) {
27
+ if (object == null || typeof object !== "object" || !("id" in object)) return null;
28
+ const id = object.id;
29
+ return id instanceof URL ? id : null;
30
+ }
31
+ /**
32
+ * Checks whether a vocabulary object is part of a chain of portable objects,
33
+ * i.e., whether it has a portable ID, has an FEP-ef61 compatible identifier
34
+ * as its ID (even a malformed one, so that it fails closed), or was obtained
35
+ * from a portable object. Accessors of such objects dereference references
36
+ * as portable objects.
37
+ *
38
+ * Being part of a chain does not mean that the object itself is verified:
39
+ * an object parsed from arbitrary JSON-LD is in a chain just because of its
40
+ * ID. Only {@link dereferencePortableIri} records that an object was
41
+ * accepted by a verifier.
42
+ *
43
+ * @internal Technically exported for generated vocabulary classes, but not
44
+ * part of the public API contract. This is not considered public API for
45
+ * Semantic Versioning decisions.
46
+ */
47
+ function isInPortableChain(object) {
48
+ if (provenances.has(object)) return true;
49
+ const id = getObjectId(object);
50
+ return id != null && (isPortableIri(id) || isCompatibleEf61Iri(id));
51
+ }
52
+ /**
53
+ * Checks whether a URL is an FEP-ef61 compatible identifier, i.e., an HTTP(S)
54
+ * URL under a gateway's `/.well-known/apgateway/` path that stands for
55
+ * a portable object. Malformed compatible identifiers count as compatible
56
+ * identifiers too.
57
+ *
58
+ * @internal Technically exported for generated vocabulary classes, but not
59
+ * part of the public API contract. This is not considered public API for
60
+ * Semantic Versioning decisions.
61
+ */
62
+ function isCompatibleEf61Iri(url) {
63
+ try {
64
+ return require_url.fromCompatibleEf61Id(url) != null;
65
+ } catch (error) {
66
+ if (error instanceof TypeError) return true;
67
+ throw error;
68
+ }
69
+ }
70
+ /**
71
+ * Records that a vocabulary object returned by a property accessor was
72
+ * obtained through the given property of a portable object, so that later
73
+ * dereferences from it can tell where it came from. Nothing is recorded if
74
+ * the parent is not in a portable chain, or if the child already has its
75
+ * provenance.
76
+ *
77
+ * @internal Technically exported for generated vocabulary classes, but not
78
+ * part of the public API contract. This is not considered public API for
79
+ * Semantic Versioning decisions.
80
+ */
81
+ function recordPortableReferrer(parent, child, property) {
82
+ if (child == null || typeof child !== "object") return;
83
+ if (provenances.has(child) || !isInPortableChain(parent)) return;
84
+ provenances.set(child, { referrer: {
85
+ object: parent,
86
+ property
87
+ } });
88
+ }
89
+ /**
90
+ * Checks whether a URL is an FEP-ef61 portable ActivityPub IRI.
91
+ *
92
+ * @internal Technically exported for generated vocabulary classes, but not
93
+ * part of the public API contract. This is not considered public API for
94
+ * Semantic Versioning decisions.
95
+ */
96
+ function isPortableIri(url) {
97
+ return url.protocol === "ap+ef61:" || url.protocol === "ap:";
98
+ }
99
+ /**
100
+ * Picks the ordered list of FEP-ef61 gateways to fetch a portable IRI from.
101
+ *
102
+ * If `gateways` is given, it is used as is (even when empty), and `@gateway`
103
+ * location hints in the IRI are ignored. Otherwise, up to
104
+ * {@link MAX_GATEWAY_HINTS} valid `@gateway` hints are used. Duplicate
105
+ * gateways are dropped in both cases.
106
+ *
107
+ * @throws {TypeError} If an explicit gateway is not an HTTP(S) origin.
108
+ * @internal
109
+ */
110
+ function getPortableGatewayCandidates(url, gateways) {
111
+ const candidates = [];
112
+ const seen = /* @__PURE__ */ new Set();
113
+ const add = (gateway) => {
114
+ if (seen.has(gateway.href)) return;
115
+ seen.add(gateway.href);
116
+ candidates.push(gateway);
117
+ };
118
+ if (gateways != null) {
119
+ for (const gateway of gateways) {
120
+ const parsed = require_url.parseGatewayOrigin(gateway);
121
+ if (parsed == null) throw new TypeError("FEP-ef61 gateways must be HTTP(S) origins with no credentials, path, query, or fragment: " + String(gateway));
122
+ add(parsed);
123
+ }
124
+ return candidates;
125
+ }
126
+ for (const hint of new URLSearchParams(url.search).getAll(require_url.GATEWAY_HINT_PARAMETER)) {
127
+ if (candidates.length >= MAX_GATEWAY_HINTS) break;
128
+ const parsed = require_url.parseGatewayOrigin(hint);
129
+ if (parsed == null) {
130
+ logger.debug("Ignoring an invalid FEP-ef61 gateway hint {hint} in {url}.", {
131
+ hint,
132
+ url: require_url.formatIri(url)
133
+ });
134
+ continue;
135
+ }
136
+ add(parsed);
137
+ }
138
+ return candidates;
139
+ }
140
+ /**
141
+ * Gets the gateways through which a portable reference without location hints
142
+ * can be dereferenced, from the portable actor that the reference belongs to.
143
+ *
144
+ * The references in a portable actor's own document, such as its `outbox`,
145
+ * usually have no `@gateway` hints, as they are not needed there, since
146
+ * the actor's `gateways` already tells where to retrieve them. So this walks
147
+ * from the object whose property is being dereferenced up through the objects
148
+ * it was obtained from, e.g., from a collection page to the collection and
149
+ * then to the actor, and returns the `gateways` of the first object that has
150
+ * any, but only if that object has the same DID as the reference. The
151
+ * gateways only tell where to look; whatever they serve is still verified.
152
+ *
153
+ * @param object The object whose property is being dereferenced.
154
+ * @param url The portable IRI to dereference.
155
+ * @returns Up to {@link MAX_GATEWAY_HINTS} valid gateways, or `undefined` if
156
+ * the IRI has valid `@gateway` hints or no such object is found.
157
+ * @internal Technically exported for generated vocabulary classes, but not
158
+ * part of the public API contract. This is not considered public API for
159
+ * Semantic Versioning decisions.
160
+ */
161
+ function getReferrerGateways(object, url) {
162
+ if (getPortableGatewayCandidates(url).length > 0) return void 0;
163
+ const visited = /* @__PURE__ */ new Set();
164
+ let current = object;
165
+ while (current != null && !visited.has(current)) {
166
+ visited.add(current);
167
+ const gateways = "gateways" in current ? current.gateways : void 0;
168
+ if (Array.isArray(gateways) && gateways.length > 0) {
169
+ const id = getObjectId(current);
170
+ const portableId = id == null ? null : isPortableIri(id) ? id : getCompatibleEf61Target(id);
171
+ if (portableId == null || !require_url.haveSameFe34Origin(portableId, url)) return;
172
+ const candidates = [];
173
+ for (const gateway of gateways) {
174
+ if (candidates.length >= MAX_GATEWAY_HINTS) break;
175
+ if (typeof gateway !== "string" && !(gateway instanceof URL)) continue;
176
+ const parsed = require_url.parseGatewayOrigin(gateway);
177
+ if (parsed == null) continue;
178
+ if (candidates.some((c) => c.href === parsed.href)) continue;
179
+ candidates.push(parsed);
180
+ }
181
+ return candidates.length > 0 ? candidates : void 0;
182
+ }
183
+ current = provenances.get(current)?.referrer?.object;
184
+ }
185
+ }
186
+ function getCompatibleEf61Target(id) {
187
+ try {
188
+ return require_url.fromCompatibleEf61Id(id);
189
+ } catch {
190
+ return null;
191
+ }
192
+ }
193
+ /**
194
+ * Creates a context loader that returns the same context documents for the
195
+ * whole dereference operation, so that the identity check, the proof
196
+ * verifier, and the parser interpret the fetched document identically even
197
+ * if the underlying loader is nondeterministic. Failed loads are not
198
+ * remembered, so a transient failure does not affect the next gateway.
199
+ *
200
+ * The parsed object keeps the loader for its own later dereferences, so
201
+ * `release()` turns it into a plain pass-through to the underlying loader
202
+ * once the operation is over.
203
+ *
204
+ * @internal Technically exported for generated vocabulary classes, but not
205
+ * part of the public API contract. This is not considered public API for
206
+ * Semantic Versioning decisions.
207
+ */
208
+ function createSnapshotContextLoader(contextLoader, suppressError) {
209
+ contextLoader = require_jsonld_cache.unwrapReleasedDocumentLoader(contextLoader);
210
+ const cache = /* @__PURE__ */ new Map();
211
+ let released = false;
212
+ const release = () => {
213
+ released = true;
214
+ state.released = true;
215
+ cache.clear();
216
+ };
217
+ const loader = async (url, options) => {
218
+ if (released) return await contextLoader(url, options);
219
+ const key = URL.canParse(url) ? new URL(url).href : url;
220
+ if (BASELINE_CONTEXT_URLS.has(key)) return {
221
+ contextUrl: null,
222
+ document: structuredClone(require_contexts.preloadedContexts[key]),
223
+ documentUrl: key
224
+ };
225
+ let promise = cache.get(key);
226
+ if (promise == null) {
227
+ const loading = contextLoader(url, suppressError ? {
228
+ ...options,
229
+ suppressError: true
230
+ } : options).then((document) => structuredClone(document));
231
+ promise = loading;
232
+ cache.set(key, loading);
233
+ loading.catch(() => {
234
+ if (cache.get(key) === loading) cache.delete(key);
235
+ });
236
+ }
237
+ return structuredClone(await promise);
238
+ };
239
+ const state = {
240
+ base: contextLoader,
241
+ released: false
242
+ };
243
+ require_jsonld_cache.registerDocumentLoaderWrapper(loader, state);
244
+ return {
245
+ loader,
246
+ release
247
+ };
248
+ }
249
+ //#endregion
250
+ Object.defineProperty(exports, "createSnapshotContextLoader", {
251
+ enumerable: true,
252
+ get: function() {
253
+ return createSnapshotContextLoader;
254
+ }
255
+ });
256
+ Object.defineProperty(exports, "getPortableGatewayCandidates", {
257
+ enumerable: true,
258
+ get: function() {
259
+ return getPortableGatewayCandidates;
260
+ }
261
+ });
262
+ Object.defineProperty(exports, "getReferrerGateways", {
263
+ enumerable: true,
264
+ get: function() {
265
+ return getReferrerGateways;
266
+ }
267
+ });
268
+ Object.defineProperty(exports, "isPortableIri", {
269
+ enumerable: true,
270
+ get: function() {
271
+ return isPortableIri;
272
+ }
273
+ });
274
+ Object.defineProperty(exports, "recordPortableReferrer", {
275
+ enumerable: true,
276
+ get: function() {
277
+ return recordPortableReferrer;
278
+ }
279
+ });
@@ -0,0 +1,153 @@
1
+ const require_request = require("./request-BgjA9wyl.cjs");
2
+ const require_body = require("./body-cKLBprYm.cjs");
3
+ const require_url = require("./url-BNakuZ8k.cjs");
4
+ const require_digest = require("./digest-3FeH2Y-Q.cjs");
5
+ //#region src/portable-media.ts
6
+ function resolveTimeout(value, fallback) {
7
+ if (value === null) return null;
8
+ const result = value ?? fallback;
9
+ if (!Number.isFinite(result) || result <= 0 || result > 2147483647) throw new RangeError("A media timeout must be a positive finite number of milliseconds.");
10
+ return Math.ceil(result);
11
+ }
12
+ async function withTimeout(operation, parent, timeout) {
13
+ parent?.throwIfAborted();
14
+ const controller = new AbortController();
15
+ const onParentAbort = () => controller.abort(parent?.reason);
16
+ parent?.addEventListener("abort", onParentAbort, { once: true });
17
+ let timer;
18
+ if (timeout != null) timer = setTimeout(() => controller.abort(new DOMException(`Media retrieval timed out after ${timeout} ms.`, "TimeoutError")), timeout);
19
+ let onAbort;
20
+ const aborted = new Promise((_, reject) => {
21
+ onAbort = () => reject(controller.signal.reason);
22
+ controller.signal.addEventListener("abort", onAbort, { once: true });
23
+ });
24
+ try {
25
+ return await Promise.race([operation(controller.signal), aborted]);
26
+ } finally {
27
+ if (timer != null) clearTimeout(timer);
28
+ parent?.removeEventListener("abort", onParentAbort);
29
+ if (onAbort != null) controller.signal.removeEventListener("abort", onAbort);
30
+ }
31
+ }
32
+ function mediaTarget(media) {
33
+ if (typeof media === "string" || media instanceof URL) return {
34
+ url: media.toString(),
35
+ digestMultibase: void 0
36
+ };
37
+ const target = media.href ?? media.url;
38
+ const url = target instanceof URL ? target.href : target != null && typeof target === "object" && "href" in target ? target.href?.href : void 0;
39
+ if (url == null) throw new TypeError("Portable media has no URL or href.");
40
+ const linkDigest = target != null && !(target instanceof URL) && typeof target === "object" && "digestMultibase" in target ? target.digestMultibase : void 0;
41
+ if (media.digestMultibase != null && linkDigest != null) {
42
+ const outer = require_digest.parseDigestMultibase(media.digestMultibase).digest;
43
+ const inner = require_digest.parseDigestMultibase(linkDigest).digest;
44
+ if (outer.some((byte, i) => byte !== inner[i])) throw new TypeError("Media and linked resource digests disagree.");
45
+ }
46
+ return {
47
+ url,
48
+ digestMultibase: media.digestMultibase ?? linkDigest
49
+ };
50
+ }
51
+ async function fetchVerified(url, digestMultibase, options, signal, maxBytes) {
52
+ const visited = /* @__PURE__ */ new Set();
53
+ for (let redirects = 0; redirects <= 20; redirects++) {
54
+ signal.throwIfAborted();
55
+ if (url.username !== "" || url.password !== "") throw new TypeError("Media URLs must not contain credentials.");
56
+ if (!options.allowPrivateAddress) await require_url.validatePublicUrl(url.href);
57
+ else if (url.protocol !== "http:" && url.protocol !== "https:") throw new TypeError("Media URLs must use HTTP(S).");
58
+ if (visited.has(url.href)) throw new TypeError("Media redirect loop.");
59
+ visited.add(url.href);
60
+ const response = await fetch(url, {
61
+ method: "GET",
62
+ headers: {
63
+ Accept: "*/*",
64
+ "User-Agent": typeof options.userAgent === "string" ? options.userAgent : require_request.getUserAgent(options.userAgent)
65
+ },
66
+ redirect: "manual",
67
+ signal
68
+ });
69
+ if ([
70
+ 301,
71
+ 302,
72
+ 303,
73
+ 307,
74
+ 308
75
+ ].includes(response.status)) {
76
+ const location = response.headers.get("Location");
77
+ response.body?.cancel().catch(() => {});
78
+ if (location == null || redirects === 20) throw new TypeError("Invalid or excessive media redirects.");
79
+ url = new URL(location, url);
80
+ continue;
81
+ }
82
+ if (response.status !== 200) {
83
+ response.body?.cancel().catch(() => {});
84
+ throw new Error(`Media request failed with HTTP ${response.status}.`);
85
+ }
86
+ const bytes = await require_body.readBoundedBytes(response, maxBytes, url);
87
+ signal.throwIfAborted();
88
+ if (!await require_digest.verifyDigestMultibase(bytes, digestMultibase)) throw new Error("Portable media digest does not match.");
89
+ const headers = new Headers();
90
+ const contentType = response.headers.get("Content-Type");
91
+ if (contentType != null) headers.set("Content-Type", contentType);
92
+ return new Response(bytes, { headers });
93
+ }
94
+ throw new TypeError("Too many media redirects.");
95
+ }
96
+ /**
97
+ * Fetches a portable object's external media and returns it only after its
98
+ * SHA-256 `digestMultibase` has been verified. `hl:` resources are tried at
99
+ * the supplied gateways in order. The result contains verified body bytes;
100
+ * its `Content-Type` is unverified metadata supplied by the server.
101
+ *
102
+ * @param media A Link, Image, Document, or URL with an external resource.
103
+ * @param options Retrieval, integrity, and network policy options.
104
+ * @returns A response containing only verified bytes.
105
+ * @throws {TypeError} If the media reference or digest is invalid.
106
+ * @throws {Error} If no source supplies a matching bounded resource.
107
+ * @since 2.4.0
108
+ */
109
+ async function fetchPortableMedia(media, options = {}) {
110
+ const { url, digestMultibase: propertyDigest } = mediaTarget(media);
111
+ const digestMultibase = propertyDigest ?? options.digestMultibase;
112
+ if (digestMultibase == null) throw new TypeError("Portable media requires digestMultibase.");
113
+ const expected = require_digest.parseDigestMultibase(digestMultibase).digest;
114
+ const maxBytes = options.maxBytes ?? 16777216;
115
+ if (!Number.isSafeInteger(maxBytes) || maxBytes <= 0) throw new RangeError("The media size limit must be a positive safe integer.");
116
+ const timeout = resolveTimeout(options.timeout, 1e4);
117
+ const gatewayTimeout = resolveTimeout(options.gatewayTimeout, 3e3);
118
+ let targets;
119
+ let hashlink = false;
120
+ if (/^hl:/i.test(url)) {
121
+ hashlink = true;
122
+ const hashDigest = require_digest.parseDigestMultibase(require_digest.parseHashlink(url).digestMultibase).digest;
123
+ if (expected.some((byte, i) => byte !== hashDigest[i])) throw new TypeError("Hashlink and digestMultibase disagree.");
124
+ const gateways = [...new Set([...options.gateways ?? []].map((gateway) => require_url.parseGatewayUrl(gateway.toString()).origin))];
125
+ if (gateways.length === 0) throw new TypeError("Hashlink media requires at least one gateway.");
126
+ targets = gateways.map((gateway) => new URL(`/.well-known/apgateway/${url}`, gateway));
127
+ } else {
128
+ const target = new URL(url);
129
+ if (target.protocol !== "http:" && target.protocol !== "https:") throw new TypeError("Portable media URLs must use HTTP(S) or hl:.");
130
+ targets = [target];
131
+ }
132
+ return await withTimeout(async (signal) => {
133
+ let lastError;
134
+ for (const target of targets) {
135
+ signal.throwIfAborted();
136
+ try {
137
+ return await withTimeout((attemptSignal) => fetchVerified(target, digestMultibase, options, attemptSignal, maxBytes), signal, hashlink ? gatewayTimeout : null);
138
+ } catch (error) {
139
+ signal.throwIfAborted();
140
+ if (!hashlink) throw error;
141
+ lastError = error;
142
+ }
143
+ }
144
+ throw new Error("No gateway returned verified portable media.", { cause: lastError });
145
+ }, options.signal, timeout);
146
+ }
147
+ //#endregion
148
+ Object.defineProperty(exports, "fetchPortableMedia", {
149
+ enumerable: true,
150
+ get: function() {
151
+ return fetchPortableMedia;
152
+ }
153
+ });
@@ -0,0 +1,148 @@
1
+ import { r as getUserAgent } from "./request-DodZNukP.mjs";
2
+ import { r as readBoundedBytes } from "./body-DpC9p-iG.mjs";
3
+ import { g as parseGatewayUrl, x as validatePublicUrl } from "./url-DMxmp7ZG.mjs";
4
+ import { a as verifyDigestMultibase, i as parseHashlink, r as parseDigestMultibase } from "./digest-COC7xDiQ.mjs";
5
+ //#region src/portable-media.ts
6
+ function resolveTimeout(value, fallback) {
7
+ if (value === null) return null;
8
+ const result = value ?? fallback;
9
+ if (!Number.isFinite(result) || result <= 0 || result > 2147483647) throw new RangeError("A media timeout must be a positive finite number of milliseconds.");
10
+ return Math.ceil(result);
11
+ }
12
+ async function withTimeout(operation, parent, timeout) {
13
+ parent?.throwIfAborted();
14
+ const controller = new AbortController();
15
+ const onParentAbort = () => controller.abort(parent?.reason);
16
+ parent?.addEventListener("abort", onParentAbort, { once: true });
17
+ let timer;
18
+ if (timeout != null) timer = setTimeout(() => controller.abort(new DOMException(`Media retrieval timed out after ${timeout} ms.`, "TimeoutError")), timeout);
19
+ let onAbort;
20
+ const aborted = new Promise((_, reject) => {
21
+ onAbort = () => reject(controller.signal.reason);
22
+ controller.signal.addEventListener("abort", onAbort, { once: true });
23
+ });
24
+ try {
25
+ return await Promise.race([operation(controller.signal), aborted]);
26
+ } finally {
27
+ if (timer != null) clearTimeout(timer);
28
+ parent?.removeEventListener("abort", onParentAbort);
29
+ if (onAbort != null) controller.signal.removeEventListener("abort", onAbort);
30
+ }
31
+ }
32
+ function mediaTarget(media) {
33
+ if (typeof media === "string" || media instanceof URL) return {
34
+ url: media.toString(),
35
+ digestMultibase: void 0
36
+ };
37
+ const target = media.href ?? media.url;
38
+ const url = target instanceof URL ? target.href : target != null && typeof target === "object" && "href" in target ? target.href?.href : void 0;
39
+ if (url == null) throw new TypeError("Portable media has no URL or href.");
40
+ const linkDigest = target != null && !(target instanceof URL) && typeof target === "object" && "digestMultibase" in target ? target.digestMultibase : void 0;
41
+ if (media.digestMultibase != null && linkDigest != null) {
42
+ const outer = parseDigestMultibase(media.digestMultibase).digest;
43
+ const inner = parseDigestMultibase(linkDigest).digest;
44
+ if (outer.some((byte, i) => byte !== inner[i])) throw new TypeError("Media and linked resource digests disagree.");
45
+ }
46
+ return {
47
+ url,
48
+ digestMultibase: media.digestMultibase ?? linkDigest
49
+ };
50
+ }
51
+ async function fetchVerified(url, digestMultibase, options, signal, maxBytes) {
52
+ const visited = /* @__PURE__ */ new Set();
53
+ for (let redirects = 0; redirects <= 20; redirects++) {
54
+ signal.throwIfAborted();
55
+ if (url.username !== "" || url.password !== "") throw new TypeError("Media URLs must not contain credentials.");
56
+ if (!options.allowPrivateAddress) await validatePublicUrl(url.href);
57
+ else if (url.protocol !== "http:" && url.protocol !== "https:") throw new TypeError("Media URLs must use HTTP(S).");
58
+ if (visited.has(url.href)) throw new TypeError("Media redirect loop.");
59
+ visited.add(url.href);
60
+ const response = await fetch(url, {
61
+ method: "GET",
62
+ headers: {
63
+ Accept: "*/*",
64
+ "User-Agent": typeof options.userAgent === "string" ? options.userAgent : getUserAgent(options.userAgent)
65
+ },
66
+ redirect: "manual",
67
+ signal
68
+ });
69
+ if ([
70
+ 301,
71
+ 302,
72
+ 303,
73
+ 307,
74
+ 308
75
+ ].includes(response.status)) {
76
+ const location = response.headers.get("Location");
77
+ response.body?.cancel().catch(() => {});
78
+ if (location == null || redirects === 20) throw new TypeError("Invalid or excessive media redirects.");
79
+ url = new URL(location, url);
80
+ continue;
81
+ }
82
+ if (response.status !== 200) {
83
+ response.body?.cancel().catch(() => {});
84
+ throw new Error(`Media request failed with HTTP ${response.status}.`);
85
+ }
86
+ const bytes = await readBoundedBytes(response, maxBytes, url);
87
+ signal.throwIfAborted();
88
+ if (!await verifyDigestMultibase(bytes, digestMultibase)) throw new Error("Portable media digest does not match.");
89
+ const headers = new Headers();
90
+ const contentType = response.headers.get("Content-Type");
91
+ if (contentType != null) headers.set("Content-Type", contentType);
92
+ return new Response(bytes, { headers });
93
+ }
94
+ throw new TypeError("Too many media redirects.");
95
+ }
96
+ /**
97
+ * Fetches a portable object's external media and returns it only after its
98
+ * SHA-256 `digestMultibase` has been verified. `hl:` resources are tried at
99
+ * the supplied gateways in order. The result contains verified body bytes;
100
+ * its `Content-Type` is unverified metadata supplied by the server.
101
+ *
102
+ * @param media A Link, Image, Document, or URL with an external resource.
103
+ * @param options Retrieval, integrity, and network policy options.
104
+ * @returns A response containing only verified bytes.
105
+ * @throws {TypeError} If the media reference or digest is invalid.
106
+ * @throws {Error} If no source supplies a matching bounded resource.
107
+ * @since 2.4.0
108
+ */
109
+ async function fetchPortableMedia(media, options = {}) {
110
+ const { url, digestMultibase: propertyDigest } = mediaTarget(media);
111
+ const digestMultibase = propertyDigest ?? options.digestMultibase;
112
+ if (digestMultibase == null) throw new TypeError("Portable media requires digestMultibase.");
113
+ const expected = parseDigestMultibase(digestMultibase).digest;
114
+ const maxBytes = options.maxBytes ?? 16777216;
115
+ if (!Number.isSafeInteger(maxBytes) || maxBytes <= 0) throw new RangeError("The media size limit must be a positive safe integer.");
116
+ const timeout = resolveTimeout(options.timeout, 1e4);
117
+ const gatewayTimeout = resolveTimeout(options.gatewayTimeout, 3e3);
118
+ let targets;
119
+ let hashlink = false;
120
+ if (/^hl:/i.test(url)) {
121
+ hashlink = true;
122
+ const hashDigest = parseDigestMultibase(parseHashlink(url).digestMultibase).digest;
123
+ if (expected.some((byte, i) => byte !== hashDigest[i])) throw new TypeError("Hashlink and digestMultibase disagree.");
124
+ const gateways = [...new Set([...options.gateways ?? []].map((gateway) => parseGatewayUrl(gateway.toString()).origin))];
125
+ if (gateways.length === 0) throw new TypeError("Hashlink media requires at least one gateway.");
126
+ targets = gateways.map((gateway) => new URL(`/.well-known/apgateway/${url}`, gateway));
127
+ } else {
128
+ const target = new URL(url);
129
+ if (target.protocol !== "http:" && target.protocol !== "https:") throw new TypeError("Portable media URLs must use HTTP(S) or hl:.");
130
+ targets = [target];
131
+ }
132
+ return await withTimeout(async (signal) => {
133
+ let lastError;
134
+ for (const target of targets) {
135
+ signal.throwIfAborted();
136
+ try {
137
+ return await withTimeout((attemptSignal) => fetchVerified(target, digestMultibase, options, attemptSignal, maxBytes), signal, hashlink ? gatewayTimeout : null);
138
+ } catch (error) {
139
+ signal.throwIfAborted();
140
+ if (!hashlink) throw error;
141
+ lastError = error;
142
+ }
143
+ }
144
+ throw new Error("No gateway returned verified portable media.", { cause: lastError });
145
+ }, options.signal, timeout);
146
+ }
147
+ //#endregion
148
+ export { fetchPortableMedia as t };