@fedify/vocab-runtime 2.4.0-pr.934.40 → 2.4.0

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 (140) hide show
  1. package/deno.json +4 -2
  2. package/dist/{tests/docloader-Xi61Le8w.mjs → contexts-BK1CqBR5.cjs} +164 -269
  3. package/dist/{tests/docloader-BFedvemb.cjs → contexts-DPuJ4UYL.js} +160 -288
  4. package/dist/{docloader-DnUMWHaJ.d.cts → docloader-CYwqh5Df.d.cts} +68 -2
  5. package/dist/{docloader-xRGn1azD.d.ts → docloader-CYwqh5Df.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 +715 -4523
  20. package/dist/mod.d.cts +406 -6
  21. package/dist/mod.d.ts +406 -7
  22. package/dist/mod.js +694 -4518
  23. package/dist/portable-DqtfLy_1.d.cts +160 -0
  24. package/dist/portable-DtWsu2yU.d.ts +160 -0
  25. package/dist/tests/body-CYiu9CO-.mjs +130 -0
  26. package/dist/tests/body-CkEwROsn.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-CIKsin4e.mjs +4512 -0
  32. package/dist/tests/contexts-DizzBjz4.cjs +4523 -0
  33. package/dist/tests/decimal.test.cjs +8 -7
  34. package/dist/tests/decimal.test.mjs +8 -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-C3YFl6y4.cjs +396 -0
  42. package/dist/tests/docloader-CvmS4sVq.mjs +373 -0
  43. package/dist/tests/docloader.test.cjs +692 -39
  44. package/dist/tests/docloader.test.mjs +686 -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-cache-BPQmOZWD.mjs +342 -0
  52. package/dist/tests/jsonld-cache-C07AyNOY.cjs +397 -0
  53. package/dist/tests/jsonld-cache.test.cjs +101 -298
  54. package/dist/tests/jsonld-cache.test.mjs +94 -289
  55. package/dist/tests/{key-_wXwomh_.cjs → key-C-AYkdJJ.cjs} +11 -4
  56. package/dist/tests/{key-CDGDH_vC.mjs → key-C2Db_TAJ.mjs} +11 -3
  57. package/dist/tests/key.test.cjs +6 -5
  58. package/dist/tests/key.test.mjs +6 -4
  59. package/dist/tests/langstr.test.cjs +4 -4
  60. package/dist/tests/langstr.test.mjs +2 -2
  61. package/dist/tests/link.test.cjs +2 -3
  62. package/dist/tests/link.test.mjs +2 -2
  63. package/dist/tests/multibase/multibase.test.cjs +8 -9
  64. package/dist/tests/multibase/multibase.test.mjs +6 -6
  65. package/dist/tests/{multibase-Bz_UUDtL.cjs → multibase-B5Mea7Ip.cjs} +19 -2
  66. package/dist/tests/{multibase-B4bvakyA.mjs → multibase-BPnF_L4e.mjs} +12 -1
  67. package/dist/tests/portable-dereference-BNXtgg5U.cjs +278 -0
  68. package/dist/tests/portable-dereference-DRE5bz-l.mjs +249 -0
  69. package/dist/tests/portable-media-BLKllU56.cjs +153 -0
  70. package/dist/tests/portable-media-DRSGBORB.mjs +148 -0
  71. package/dist/tests/portable-media.test.cjs +222 -0
  72. package/dist/tests/portable-media.test.d.cts +1 -0
  73. package/dist/tests/portable-media.test.d.mts +1 -0
  74. package/dist/tests/portable-media.test.mjs +223 -0
  75. package/dist/tests/portable-workers.test.cjs +36 -0
  76. package/dist/tests/portable-workers.test.d.cts +2 -0
  77. package/dist/tests/portable-workers.test.d.mts +2 -0
  78. package/dist/tests/portable-workers.test.mjs +35 -0
  79. package/dist/tests/{request-cCPgOxYG.cjs → request-8vPWtV1-.cjs} +10 -4
  80. package/dist/tests/{request-BOS-hNaf.mjs → request-DRaOaTqD.mjs} +8 -2
  81. package/dist/tests/request.test.cjs +7 -4
  82. package/dist/tests/request.test.mjs +5 -2
  83. package/dist/tests/signed-representation.test.cjs +588 -0
  84. package/dist/tests/signed-representation.test.d.cts +1 -0
  85. package/dist/tests/signed-representation.test.d.mts +1 -0
  86. package/dist/tests/signed-representation.test.mjs +589 -0
  87. package/dist/tests/temporal.test.cjs +1 -2
  88. package/dist/tests/temporal.test.mjs +1 -1
  89. package/dist/tests/url-BNakuZ8k.cjs +998 -0
  90. package/dist/tests/url-DMxmp7ZG.mjs +859 -0
  91. package/dist/tests/url.test.cjs +503 -3
  92. package/dist/tests/url.test.mjs +503 -2
  93. package/dist/url-DrGTR8yv.cjs +993 -0
  94. package/dist/url-Dzyp-NsC.js +860 -0
  95. package/package.json +27 -4
  96. package/scripts/test-bun.mjs +17 -0
  97. package/src/body.test.ts +125 -0
  98. package/src/body.ts +152 -0
  99. package/src/contexts/cid-v1.json +114 -0
  100. package/src/contexts/fep-22cd.json +21 -0
  101. package/src/contexts/fep-ef61.json +10 -0
  102. package/src/contexts/miscellany.json +17 -0
  103. package/src/contexts/security-data-integrity-v1.json +0 -4
  104. package/src/contexts.ts +40 -0
  105. package/src/digest.test.ts +220 -0
  106. package/src/digest.ts +229 -0
  107. package/src/docloader.test.ts +918 -27
  108. package/src/docloader.ts +322 -105
  109. package/src/internal/jsonld-cache.ts +97 -1
  110. package/src/internal/portable-dereference.test.ts +254 -0
  111. package/src/internal/portable-dereference.ts +1000 -0
  112. package/src/internal/signed-representation.ts +565 -0
  113. package/src/jsonld-cache.test.ts +89 -0
  114. package/src/key.test.ts +9 -0
  115. package/src/key.ts +11 -1
  116. package/src/mod.ts +30 -0
  117. package/src/multibase/multibase.test.ts +5 -5
  118. package/src/portable-media.test.ts +293 -0
  119. package/src/portable-media.ts +264 -0
  120. package/src/portable-workers.test.ts +81 -0
  121. package/src/portable.ts +180 -0
  122. package/src/preprocessor.ts +7 -0
  123. package/src/request.test.ts +10 -1
  124. package/src/request.ts +9 -1
  125. package/src/signed-representation.test.ts +338 -0
  126. package/src/url.test.ts +844 -1
  127. package/src/url.ts +717 -17
  128. package/tsdown.config.ts +2 -0
  129. package/dist/tests/url-BvjYQdxL.cjs +0 -456
  130. package/dist/tests/url-a2D8NAgh.mjs +0 -378
  131. package/dist/url-Ck3dGEwH.cjs +0 -457
  132. package/dist/url-m1YxGNZ0.js +0 -379
  133. /package/dist/{chunk-M78iaK0I.cjs → rolldown-runtime-B7lfambq.cjs} +0 -0
  134. /package/dist/tests/{langstr-CbAxaeEZ.cjs → langstr-C4Fl80ae.cjs} +0 -0
  135. /package/dist/tests/{langstr-Di5AvKpB.mjs → langstr-CQ26J_L7.mjs} +0 -0
  136. /package/dist/tests/{link-NUUWCdnK.mjs → link-Cevmc87v.mjs} +0 -0
  137. /package/dist/tests/{link-FguCydMA.cjs → link-DlKm8bEr.cjs} +0 -0
  138. /package/dist/tests/{multicodec-CxGVGa91.cjs → multicodec-CLRPeW4N.cjs} +0 -0
  139. /package/dist/tests/{multicodec-CyFp54fI.mjs → multicodec-CRIj_05H.mjs} +0 -0
  140. /package/dist/tests/{chunk-C2EiDwsr.cjs → rolldown-runtime-emK7D4bc.cjs} +0 -0
@@ -0,0 +1,721 @@
1
+
2
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
+ const require_contexts = require("../contexts-BK1CqBR5.cjs");
4
+ const require_url = require("../url-DrGTR8yv.cjs");
5
+ const require_jsonld = require("../jsonld.cjs");
6
+ const require_internal_jsonld_cache = require("./jsonld-cache.cjs");
7
+ let _logtape_logtape = require("@logtape/logtape");
8
+ let _opentelemetry_api = require("@opentelemetry/api");
9
+ //#region src/internal/portable-dereference.ts
10
+ const logger = (0, _logtape_logtape.getLogger)([
11
+ "fedify",
12
+ "vocab",
13
+ "gateway"
14
+ ]);
15
+ /**
16
+ * The maximum number of `@gateway` location hints to try for one reference.
17
+ * Hints come from possibly untrusted documents, so they are bounded to keep
18
+ * a single accessor call from fanning out to many servers.
19
+ */
20
+ const MAX_GATEWAY_HINTS = 5;
21
+ const BASELINE_CONTEXT_URLS = /* @__PURE__ */ new Set([
22
+ "https://w3id.org/identity/v1",
23
+ "https://www.w3.org/ns/activitystreams",
24
+ "https://w3id.org/security/v1",
25
+ "https://w3id.org/security/data-integrity/v1"
26
+ ]);
27
+ /**
28
+ * The maximum number of links in a referrer chain passed to a
29
+ * {@link PortableObjectVerifier}. Longer chains are cut off and marked as
30
+ * truncated.
31
+ */
32
+ const MAX_REFERRER_CHAIN_LENGTH = 32;
33
+ const provenances = /* @__PURE__ */ new WeakMap();
34
+ function getObjectId(object) {
35
+ if (object == null || typeof object !== "object" || !("id" in object)) return null;
36
+ const id = object.id;
37
+ return id instanceof URL ? id : null;
38
+ }
39
+ /**
40
+ * Checks whether a vocabulary object is part of a chain of portable objects,
41
+ * i.e., whether it has a portable ID, has an FEP-ef61 compatible identifier
42
+ * as its ID (even a malformed one, so that it fails closed), or was obtained
43
+ * from a portable object. Accessors of such objects dereference references
44
+ * as portable objects.
45
+ *
46
+ * Being part of a chain does not mean that the object itself is verified:
47
+ * an object parsed from arbitrary JSON-LD is in a chain just because of its
48
+ * ID. Only {@link dereferencePortableIri} records that an object was
49
+ * accepted by a verifier.
50
+ *
51
+ * @internal Technically exported for generated vocabulary classes, but not
52
+ * part of the public API contract. This is not considered public API for
53
+ * Semantic Versioning decisions.
54
+ */
55
+ function isInPortableChain(object) {
56
+ if (provenances.has(object)) return true;
57
+ const id = getObjectId(object);
58
+ return id != null && (isPortableIri(id) || isCompatibleEf61Iri(id));
59
+ }
60
+ /**
61
+ * Checks whether a vocabulary object was accepted without an integrity
62
+ * proof, e.g., as an unsecured portable collection served by a trusted
63
+ * gateway. Objects embedded in such an object must not be trusted because
64
+ * of their origin.
65
+ *
66
+ * @internal Technically exported for generated vocabulary classes, but not
67
+ * part of the public API contract. This is not considered public API for
68
+ * Semantic Versioning decisions.
69
+ */
70
+ function isUnsecuredPortableObject(object) {
71
+ return provenances.get(object)?.acceptance === "unsecured";
72
+ }
73
+ /**
74
+ * Checks whether a URL is an FEP-ef61 compatible identifier, i.e., an HTTP(S)
75
+ * URL under a gateway's `/.well-known/apgateway/` path that stands for
76
+ * a portable object. Malformed compatible identifiers count as compatible
77
+ * identifiers too.
78
+ *
79
+ * @internal Technically exported for generated vocabulary classes, but not
80
+ * part of the public API contract. This is not considered public API for
81
+ * Semantic Versioning decisions.
82
+ */
83
+ function isCompatibleEf61Iri(url) {
84
+ try {
85
+ return require_url.fromCompatibleEf61Id(url) != null;
86
+ } catch (error) {
87
+ if (error instanceof TypeError) return true;
88
+ throw error;
89
+ }
90
+ }
91
+ /**
92
+ * Records that a vocabulary object returned by a property accessor was
93
+ * obtained through the given property of a portable object, so that later
94
+ * dereferences from it can tell where it came from. Nothing is recorded if
95
+ * the parent is not in a portable chain, or if the child already has its
96
+ * provenance.
97
+ *
98
+ * @internal Technically exported for generated vocabulary classes, but not
99
+ * part of the public API contract. This is not considered public API for
100
+ * Semantic Versioning decisions.
101
+ */
102
+ function recordPortableReferrer(parent, child, property) {
103
+ if (child == null || typeof child !== "object") return;
104
+ if (provenances.has(child) || !isInPortableChain(parent)) return;
105
+ provenances.set(child, { referrer: {
106
+ object: parent,
107
+ property
108
+ } });
109
+ }
110
+ /**
111
+ * Copies the provenance of a vocabulary object to its clone. The clone
112
+ * keeps where the original came from and the restriction on unsecured
113
+ * objects, but not a positive verification status nor a collection context,
114
+ * since the clone may have different property values.
115
+ *
116
+ * @internal Technically exported for generated vocabulary classes, but not
117
+ * part of the public API contract. This is not considered public API for
118
+ * Semantic Versioning decisions.
119
+ */
120
+ function copyPortableProvenance(from, to) {
121
+ const provenance = provenances.get(from);
122
+ if (provenance == null) return;
123
+ provenances.set(to, {
124
+ referrer: provenance.referrer,
125
+ ...provenance.acceptance === "unsecured" ? { acceptance: "unsecured" } : {}
126
+ });
127
+ }
128
+ /**
129
+ * Recognizes an FEP-ef61 compatible identifier to dereference as a portable
130
+ * object.
131
+ *
132
+ * @returns `undefined` if the URL is not a compatible identifier; `null` if
133
+ * it is a malformed compatible identifier; otherwise, the portable
134
+ * ID it stands for and the gateway it names.
135
+ * @internal Technically exported for generated vocabulary classes, but not
136
+ * part of the public API contract. This is not considered public API for
137
+ * Semantic Versioning decisions.
138
+ */
139
+ function parseCompatibleEf61Reference(url) {
140
+ try {
141
+ const id = require_url.fromCompatibleEf61Id(url);
142
+ if (id == null) return void 0;
143
+ if (typeof url === "string") url = new URL(url);
144
+ return {
145
+ id,
146
+ gateway: new URL(url.origin)
147
+ };
148
+ } catch (error) {
149
+ if (error instanceof TypeError) {
150
+ logger.debug("Invalid FEP-ef61 compatible identifier {url}: {error}", {
151
+ url: typeof url === "string" ? url : url.href,
152
+ error
153
+ });
154
+ return null;
155
+ }
156
+ throw error;
157
+ }
158
+ }
159
+ /**
160
+ * Checks whether a property accessor call on a vocabulary object applies the
161
+ * FEP-ef61 portable object policy, i.e., whether the `verifyPortableObject`
162
+ * option is given or the object is part of a chain of portable objects.
163
+ * In this mode, compatible identifiers are dereferenced as portable objects,
164
+ * and fetched documents that turn out to stand for portable objects are
165
+ * verified as such.
166
+ *
167
+ * @internal Technically exported for generated vocabulary classes, but not
168
+ * part of the public API contract. This is not considered public API for
169
+ * Semantic Versioning decisions.
170
+ */
171
+ function isPortableMode(object, options) {
172
+ return options.verifyPortableObject != null || isInPortableChain(object);
173
+ }
174
+ /**
175
+ * Checks whether an object embedded in a property of a vocabulary object has
176
+ * to be dereferenced by its `@id` and verified as a portable object rather
177
+ * than trusted as embedded, even with `crossOrigin: "trust"`. This is the
178
+ * case in {@link isPortableMode | portable mode} if its `@id` is an FEP-ef61
179
+ * compatible identifier, or a portable IRI whose DID differs from the
180
+ * owner's portable ID.
181
+ *
182
+ * @param parent The object whose property embeds the object.
183
+ * @param ownerId The ID of the object that vouches for embedded objects, or
184
+ * `null` if embedded objects are not trusted by their owner.
185
+ * @param id The `@id` of the embedded object.
186
+ * @param options The options of the accessor.
187
+ * @internal Technically exported for generated vocabulary classes, but not
188
+ * part of the public API contract. This is not considered public API for
189
+ * Semantic Versioning decisions.
190
+ */
191
+ function mustDereferencePortableObject(parent, ownerId, id, options) {
192
+ if (!isPortableMode(parent, options)) return false;
193
+ if (isCompatibleEf61Iri(id)) return true;
194
+ if (!isPortableIri(id)) return false;
195
+ return ownerId == null || !isPortableIri(ownerId) || !require_url.haveSameFe34Origin(ownerId, id);
196
+ }
197
+ const PORTABLE_IRI_PATTERN = /^ap(?:\+ef61)?:/i;
198
+ /**
199
+ * Finds out whether a document fetched as an ordinary HTTP(S) document
200
+ * claims to be a portable object: its final URL is a portable IRI or an
201
+ * FEP-ef61 compatible identifier, or else its `@id` is. The final URL takes
202
+ * precedence, so a document whose `@id` claims another object fails the
203
+ * identity check against the final URL.
204
+ *
205
+ * @param documentUrl The final URL of the document.
206
+ * @param objectId The `@id` of the parsed document.
207
+ * @returns `undefined` if the document does not claim to be a portable
208
+ * object; `null` if the claim is malformed; otherwise, the claim.
209
+ * @internal Technically exported for generated vocabulary classes, but not
210
+ * part of the public API contract. This is not considered public API for
211
+ * Semantic Versioning decisions.
212
+ */
213
+ function getPortableResponseClaim(documentUrl, objectId) {
214
+ if (PORTABLE_IRI_PATTERN.test(documentUrl)) {
215
+ let id;
216
+ try {
217
+ id = require_url.parseIri(documentUrl);
218
+ if (require_url.canonicalizePortableUri(documentUrl) !== require_url.canonicalizePortableUri(require_url.formatIri(id))) return null;
219
+ } catch (error) {
220
+ if (error instanceof TypeError) return null;
221
+ throw error;
222
+ }
223
+ return {
224
+ id,
225
+ inferredGateways: getPortableGatewayCandidates(id)
226
+ };
227
+ }
228
+ for (const url of [documentUrl, objectId]) {
229
+ if (url == null) continue;
230
+ const compatible = parseCompatibleEf61Reference(url);
231
+ if (compatible === null) return null;
232
+ if (compatible != null) return {
233
+ id: compatible.id,
234
+ inferredGateways: [compatible.gateway]
235
+ };
236
+ if (url instanceof URL && isPortableIri(url)) return {
237
+ id: url,
238
+ inferredGateways: getPortableGatewayCandidates(url)
239
+ };
240
+ }
241
+ }
242
+ const unverifiedPortableClaims = /* @__PURE__ */ new WeakSet();
243
+ /**
244
+ * Records that an object fetched as an ordinary HTTP(S) object stands for
245
+ * a portable object if its reference, its final URL, or its `@id` is
246
+ * a portable IRI or an FEP-ef61 compatible identifier. Accessors do not
247
+ * cache such objects in their parents, so that a later call with the
248
+ * `verifyPortableObject` option dereferences and verifies them.
249
+ *
250
+ * @internal Technically exported for generated vocabulary classes, but not
251
+ * part of the public API contract. This is not considered public API for
252
+ * Semantic Versioning decisions.
253
+ */
254
+ function markUnverifiedPortableClaim(object, reference, documentUrl) {
255
+ const objectId = getObjectId(object);
256
+ if (isPortableIri(reference) || isCompatibleEf61Iri(reference) || PORTABLE_IRI_PATTERN.test(documentUrl) || isCompatibleEf61Iri(documentUrl) || objectId != null && (isPortableIri(objectId) || isCompatibleEf61Iri(objectId))) unverifiedPortableClaims.add(object);
257
+ }
258
+ /**
259
+ * Checks whether an object was fetched as an ordinary HTTP(S) object although
260
+ * it stands for a portable object. See {@link markUnverifiedPortableClaim}.
261
+ *
262
+ * @internal Technically exported for generated vocabulary classes, but not
263
+ * part of the public API contract. This is not considered public API for
264
+ * Semantic Versioning decisions.
265
+ */
266
+ function isUnverifiedPortableClaim(object) {
267
+ return unverifiedPortableClaims.has(object);
268
+ }
269
+ /**
270
+ * Rejects a malformed FEP-ef61 compatible identifier found in a reference,
271
+ * the final URL of a fetched document, or its `@id`, when it would be
272
+ * dereferenced as a portable object. It is rejected without a request (or
273
+ * without returning the fetched document), since it cannot be verified as
274
+ * a portable object, and fetching it as an ordinary HTTP(S) object would
275
+ * skip the portable object policy.
276
+ *
277
+ * @param url The rejected URL, for the log message.
278
+ * @param options The options of the accessor.
279
+ * @returns Always `null`.
280
+ * @throws {Error} If `crossOrigin` is `"throw"` and `suppressError` is not
281
+ * set.
282
+ * @internal Technically exported for generated vocabulary classes, but not
283
+ * part of the public API contract. This is not considered public API for
284
+ * Semantic Versioning decisions.
285
+ */
286
+ function rejectMalformedCompatibleReference(url, options) {
287
+ const message = "Refusing to dereference {url}, because it is a malformed FEP-ef61 compatible identifier, which cannot be verified as a portable object.";
288
+ if (options.crossOrigin === "throw" && !options.suppressError) throw new PortableObjectRejectedError(message.replace("{url}", url));
289
+ logger.warn(message, { url });
290
+ return null;
291
+ }
292
+ /**
293
+ * Logs that an embedded object without `@id` in an object accepted without
294
+ * an integrity proof is dropped, because it cannot be dereferenced and
295
+ * verified on its own.
296
+ *
297
+ * @internal Technically exported for generated vocabulary classes, but not
298
+ * part of the public API contract. This is not considered public API for
299
+ * Semantic Versioning decisions.
300
+ */
301
+ function warnUnverifiableEmbeddedObject(parent, property) {
302
+ logger.warn("Dropping an embedded object without @id in the {property} property of {parentId}, because the parent was accepted without an integrity proof and the embedded object cannot be verified on its own.", {
303
+ property,
304
+ parentId: getObjectId(parent)?.href ?? null
305
+ });
306
+ }
307
+ function buildReferrerChain(link, depth = 0) {
308
+ if (link == null) return void 0;
309
+ const provenance = provenances.get(link.object);
310
+ const base = {
311
+ object: link.object,
312
+ id: getObjectId(link.object),
313
+ property: link.property,
314
+ ...provenance?.acceptance == null ? {} : { acceptance: provenance.acceptance },
315
+ ...provenance?.collectionContext === void 0 ? {} : { collectionContext: provenance.collectionContext }
316
+ };
317
+ if (provenance?.referrer == null) return base;
318
+ if (depth + 1 >= MAX_REFERRER_CHAIN_LENGTH) return {
319
+ ...base,
320
+ truncated: true
321
+ };
322
+ return {
323
+ ...base,
324
+ referrer: buildReferrerChain(provenance.referrer, depth + 1)
325
+ };
326
+ }
327
+ /**
328
+ * Checks whether a URL is an FEP-ef61 portable ActivityPub IRI.
329
+ *
330
+ * @internal Technically exported for generated vocabulary classes, but not
331
+ * part of the public API contract. This is not considered public API for
332
+ * Semantic Versioning decisions.
333
+ */
334
+ function isPortableIri(url) {
335
+ return url.protocol === "ap+ef61:" || url.protocol === "ap:";
336
+ }
337
+ /**
338
+ * Picks the ordered list of FEP-ef61 gateways to fetch a portable IRI from.
339
+ *
340
+ * If `gateways` is given, it is used as is (even when empty), and `@gateway`
341
+ * location hints in the IRI are ignored. Otherwise, up to
342
+ * {@link MAX_GATEWAY_HINTS} valid `@gateway` hints are used. Duplicate
343
+ * gateways are dropped in both cases.
344
+ *
345
+ * @throws {TypeError} If an explicit gateway is not an HTTP(S) origin.
346
+ * @internal
347
+ */
348
+ function getPortableGatewayCandidates(url, gateways) {
349
+ const candidates = [];
350
+ const seen = /* @__PURE__ */ new Set();
351
+ const add = (gateway) => {
352
+ if (seen.has(gateway.href)) return;
353
+ seen.add(gateway.href);
354
+ candidates.push(gateway);
355
+ };
356
+ if (gateways != null) {
357
+ for (const gateway of gateways) {
358
+ const parsed = require_url.parseGatewayOrigin(gateway);
359
+ if (parsed == null) throw new TypeError("FEP-ef61 gateways must be HTTP(S) origins with no credentials, path, query, or fragment: " + String(gateway));
360
+ add(parsed);
361
+ }
362
+ return candidates;
363
+ }
364
+ for (const hint of new URLSearchParams(url.search).getAll(require_url.GATEWAY_HINT_PARAMETER)) {
365
+ if (candidates.length >= MAX_GATEWAY_HINTS) break;
366
+ const parsed = require_url.parseGatewayOrigin(hint);
367
+ if (parsed == null) {
368
+ logger.debug("Ignoring an invalid FEP-ef61 gateway hint {hint} in {url}.", {
369
+ hint,
370
+ url: require_url.formatIri(url)
371
+ });
372
+ continue;
373
+ }
374
+ add(parsed);
375
+ }
376
+ return candidates;
377
+ }
378
+ /**
379
+ * Gets the gateways through which a portable reference without location hints
380
+ * can be dereferenced, from the portable actor that the reference belongs to.
381
+ *
382
+ * The references in a portable actor's own document, such as its `outbox`,
383
+ * usually have no `@gateway` hints, as they are not needed there, since
384
+ * the actor's `gateways` already tells where to retrieve them. So this walks
385
+ * from the object whose property is being dereferenced up through the objects
386
+ * it was obtained from, e.g., from a collection page to the collection and
387
+ * then to the actor, and returns the `gateways` of the first object that has
388
+ * any, but only if that object has the same DID as the reference. The
389
+ * gateways only tell where to look; whatever they serve is still verified.
390
+ *
391
+ * @param object The object whose property is being dereferenced.
392
+ * @param url The portable IRI to dereference.
393
+ * @returns Up to {@link MAX_GATEWAY_HINTS} valid gateways, or `undefined` if
394
+ * the IRI has valid `@gateway` hints or no such object is found.
395
+ * @internal Technically exported for generated vocabulary classes, but not
396
+ * part of the public API contract. This is not considered public API for
397
+ * Semantic Versioning decisions.
398
+ */
399
+ function getReferrerGateways(object, url) {
400
+ if (getPortableGatewayCandidates(url).length > 0) return void 0;
401
+ const visited = /* @__PURE__ */ new Set();
402
+ let current = object;
403
+ while (current != null && !visited.has(current)) {
404
+ visited.add(current);
405
+ const gateways = "gateways" in current ? current.gateways : void 0;
406
+ if (Array.isArray(gateways) && gateways.length > 0) {
407
+ const id = getObjectId(current);
408
+ const portableId = id == null ? null : isPortableIri(id) ? id : getCompatibleEf61Target(id);
409
+ if (portableId == null || !require_url.haveSameFe34Origin(portableId, url)) return;
410
+ const candidates = [];
411
+ for (const gateway of gateways) {
412
+ if (candidates.length >= MAX_GATEWAY_HINTS) break;
413
+ if (typeof gateway !== "string" && !(gateway instanceof URL)) continue;
414
+ const parsed = require_url.parseGatewayOrigin(gateway);
415
+ if (parsed == null) continue;
416
+ if (candidates.some((c) => c.href === parsed.href)) continue;
417
+ candidates.push(parsed);
418
+ }
419
+ return candidates.length > 0 ? candidates : void 0;
420
+ }
421
+ current = provenances.get(current)?.referrer?.object;
422
+ }
423
+ }
424
+ function getCompatibleEf61Target(id) {
425
+ try {
426
+ return require_url.fromCompatibleEf61Id(id);
427
+ } catch {
428
+ return null;
429
+ }
430
+ }
431
+ /**
432
+ * Creates a context loader that returns the same context documents for the
433
+ * whole dereference operation, so that the identity check, the proof
434
+ * verifier, and the parser interpret the fetched document identically even
435
+ * if the underlying loader is nondeterministic. Failed loads are not
436
+ * remembered, so a transient failure does not affect the next gateway.
437
+ *
438
+ * The parsed object keeps the loader for its own later dereferences, so
439
+ * `release()` turns it into a plain pass-through to the underlying loader
440
+ * once the operation is over.
441
+ *
442
+ * @internal Technically exported for generated vocabulary classes, but not
443
+ * part of the public API contract. This is not considered public API for
444
+ * Semantic Versioning decisions.
445
+ */
446
+ function createSnapshotContextLoader(contextLoader, suppressError) {
447
+ contextLoader = require_internal_jsonld_cache.unwrapReleasedDocumentLoader(contextLoader);
448
+ const cache = /* @__PURE__ */ new Map();
449
+ let released = false;
450
+ const release = () => {
451
+ released = true;
452
+ state.released = true;
453
+ cache.clear();
454
+ };
455
+ const loader = async (url, options) => {
456
+ if (released) return await contextLoader(url, options);
457
+ const key = URL.canParse(url) ? new URL(url).href : url;
458
+ if (BASELINE_CONTEXT_URLS.has(key)) return {
459
+ contextUrl: null,
460
+ document: structuredClone(require_contexts.preloadedContexts[key]),
461
+ documentUrl: key
462
+ };
463
+ let promise = cache.get(key);
464
+ if (promise == null) {
465
+ const loading = contextLoader(url, suppressError ? {
466
+ ...options,
467
+ suppressError: true
468
+ } : options).then((document) => structuredClone(document));
469
+ promise = loading;
470
+ cache.set(key, loading);
471
+ loading.catch(() => {
472
+ if (cache.get(key) === loading) cache.delete(key);
473
+ });
474
+ }
475
+ return structuredClone(await promise);
476
+ };
477
+ const state = {
478
+ base: contextLoader,
479
+ released: false
480
+ };
481
+ require_internal_jsonld_cache.registerDocumentLoaderWrapper(loader, state);
482
+ return {
483
+ loader,
484
+ release
485
+ };
486
+ }
487
+ /**
488
+ * The error thrown by {@link dereferencePortableIri} under
489
+ * `crossOrigin: "throw"` when gateways returned objects but none of them
490
+ * satisfied the identity and proof checks.
491
+ *
492
+ * @internal Not part of the public API contract.
493
+ */
494
+ var PortableObjectRejectedError = class extends Error {
495
+ constructor(message) {
496
+ super(message);
497
+ this.name = "PortableObjectRejectedError";
498
+ }
499
+ };
500
+ var PortableObjectRejection = class {
501
+ message;
502
+ values;
503
+ constructor(message, values = {}) {
504
+ this.message = message;
505
+ this.values = values;
506
+ }
507
+ };
508
+ /**
509
+ * Dereferences an FEP-ef61 portable IRI through its gateways.
510
+ *
511
+ * Gateways are tried one by one until one of them returns a single-node
512
+ * document whose `@id`, if any, canonically matches the requested IRI and
513
+ * which passes `verifyPortableObject`. When there is no gateway to try, the
514
+ * document loader is asked for the portable IRI itself, and its result is
515
+ * validated the same way. If the `response` option is given, only that
516
+ * document is validated, and nothing is fetched.
517
+ *
518
+ * @returns The parsed object, or `null` if no valid object was retrieved and
519
+ * `suppressError` is set or the failure was a rejected object
520
+ * (unless `crossOrigin` is `"throw"`).
521
+ * @throws {TypeError} If `gateways` has an invalid entry (always), if no
522
+ * verifier is given, or if the IRI cannot be turned into
523
+ * a gateway URL (unless `suppressError` is set).
524
+ * @internal Technically exported for generated vocabulary classes, but not
525
+ * part of the public API contract. This is not considered public API for
526
+ * Semantic Versioning decisions.
527
+ */
528
+ async function dereferencePortableIri(url, options) {
529
+ url = require_url.parseIri(url);
530
+ const lookupUrl = require_url.formatIri(url);
531
+ const { span } = options;
532
+ const explicitGateways = options.gateways == null ? void 0 : getPortableGatewayCandidates(url, options.gateways);
533
+ const inferredGateways = options.inferredGateways ?? (explicitGateways == null ? getPortableGatewayCandidates(url) : []);
534
+ const gateways = [...inferredGateways];
535
+ for (const gateway of explicitGateways ?? []) if (!gateways.some((g) => g.href === gateway.href)) gateways.push(gateway);
536
+ const fail = (error) => {
537
+ span?.setStatus({
538
+ code: _opentelemetry_api.SpanStatusCode.ERROR,
539
+ message: String(error)
540
+ });
541
+ if (options.suppressError) {
542
+ logger.warn("Failed to dereference {url}: {error}", {
543
+ url: lookupUrl,
544
+ error
545
+ });
546
+ return null;
547
+ }
548
+ throw error;
549
+ };
550
+ const verify = options.verifyPortableObject;
551
+ if (verify == null) return fail(/* @__PURE__ */ new TypeError("Dereferencing the portable object " + lookupUrl + " requires the verifyPortableObject option, e.g., verifyPortableObject() from @fedify/fedify. Passing a Fedify Context as the options gives it, and objects parsed with the option use it by default."));
552
+ const requestUrls = [];
553
+ let expectedId;
554
+ try {
555
+ expectedId = require_url.canonicalizePortableUri(lookupUrl);
556
+ if (options.response != null) requestUrls.push({
557
+ url: options.response.documentUrl,
558
+ gateway: null,
559
+ response: options.response
560
+ });
561
+ else for (const gateway of gateways) requestUrls.push({
562
+ url: require_url.toCompatibleEf61Id(url, gateway).href,
563
+ gateway
564
+ });
565
+ } catch (error) {
566
+ return fail(error);
567
+ }
568
+ if (requestUrls.length < 1) requestUrls.push({
569
+ url: lookupUrl,
570
+ gateway: null
571
+ });
572
+ const snapshot = createSnapshotContextLoader(options.contextLoader, options.suppressError);
573
+ const contextLoader = snapshot.loader;
574
+ const scopedDocument = require_internal_jsonld_cache.createScopedContextLoader(options.documentLoader, options.suppressError);
575
+ const attempts = [];
576
+ const { signal } = options;
577
+ const gatewayHints = explicitGateways == null && inferredGateways.length > 0 ? inferredGateways : void 0;
578
+ const referrer = buildReferrerChain(options.referrer);
579
+ try {
580
+ for (const { url: requestUrl, gateway, response } of requestUrls) {
581
+ signal?.throwIfAborted();
582
+ let remoteDocument;
583
+ try {
584
+ remoteDocument = response ?? await options.documentLoader(requestUrl, {
585
+ signal,
586
+ suppressError: options.suppressError
587
+ });
588
+ } catch (error) {
589
+ signal?.throwIfAborted();
590
+ logger.debug("Failed to fetch {url} from {requestUrl}: {error}", {
591
+ url: lookupUrl,
592
+ requestUrl,
593
+ error
594
+ });
595
+ attempts.push({
596
+ type: "error",
597
+ error
598
+ });
599
+ continue;
600
+ }
601
+ const { document } = remoteDocument;
602
+ try {
603
+ await checkPortableObjectId(document, expectedId, contextLoader);
604
+ let documentUrl;
605
+ try {
606
+ documentUrl = require_url.parseIri(remoteDocument.documentUrl);
607
+ } catch {
608
+ documentUrl = void 0;
609
+ }
610
+ let result;
611
+ try {
612
+ result = await verify(document, {
613
+ documentLoader: scopedDocument.loader,
614
+ contextLoader,
615
+ tracerProvider: options.tracerProvider,
616
+ ...documentUrl == null ? {} : { documentUrl },
617
+ ...explicitGateways == null ? {} : { gateways: explicitGateways },
618
+ ...gatewayHints == null ? {} : { gatewayHints },
619
+ ...referrer == null ? {} : { referrer }
620
+ });
621
+ } catch (error) {
622
+ throw new PortableObjectRejection("the proof verifier failed: {error}", { error });
623
+ }
624
+ if (!result.verified) throw new PortableObjectRejection("it does not satisfy the FEP-ef61 proof policy: {result}", { result });
625
+ const object = await options.parse(document, {
626
+ contextLoader,
627
+ baseUrl: url
628
+ });
629
+ signal?.throwIfAborted();
630
+ if (object != null && typeof object === "object") provenances.set(object, {
631
+ acceptance: result.unsecured === true ? "unsecured" : "verified",
632
+ ...result.collectionContext === void 0 ? {} : { collectionContext: result.collectionContext },
633
+ ...options.referrer == null ? {} : { referrer: options.referrer }
634
+ });
635
+ if (gateway != null) span?.setAttribute("activitypub.gateway", gateway.href);
636
+ return object;
637
+ } catch (error) {
638
+ signal?.throwIfAborted();
639
+ if (error instanceof PortableObjectRejection) {
640
+ logger.warn("Rejected the portable object {url} served from {requestUrl}, because " + error.message, {
641
+ ...error.values,
642
+ url: lookupUrl,
643
+ requestUrl
644
+ });
645
+ attempts.push({
646
+ type: "rejected",
647
+ source: requestUrl
648
+ });
649
+ continue;
650
+ }
651
+ logger.debug("Failed to parse {url} from {requestUrl}: {error}", {
652
+ url: lookupUrl,
653
+ requestUrl,
654
+ error
655
+ });
656
+ attempts.push({
657
+ type: "error",
658
+ error
659
+ });
660
+ }
661
+ }
662
+ } finally {
663
+ scopedDocument.release();
664
+ snapshot.release();
665
+ }
666
+ const rejected = attempts.filter((a) => a.type === "rejected");
667
+ if (rejected.length > 0) {
668
+ const message = "No gateway returned a valid portable object for " + lookupUrl + "; refusing to return the object. Objects retrieved from: " + rejected.map((a) => a.source).join(", ") + ".";
669
+ if (options.suppressError || options.crossOrigin !== "throw") {
670
+ span?.setStatus({
671
+ code: _opentelemetry_api.SpanStatusCode.ERROR,
672
+ message
673
+ });
674
+ logger.warn(message);
675
+ return null;
676
+ }
677
+ throw new PortableObjectRejectedError(message);
678
+ }
679
+ const errors = attempts.flatMap((a) => a.type === "error" ? [a.error] : []);
680
+ return fail(errors.length === 1 ? errors[0] : new AggregateError(errors, "Failed to dereference " + lookupUrl + " through any of its gateways: " + requestUrls.map((r) => r.url).join(", ")));
681
+ }
682
+ async function checkPortableObjectId(document, expectedId, contextLoader) {
683
+ if (document == null || typeof document !== "object" || Array.isArray(document)) throw new PortableObjectRejection("it is not a single JSON object");
684
+ const expanded = await require_jsonld.expand(document, {
685
+ documentLoader: contextLoader,
686
+ keepFreeFloatingNodes: true
687
+ });
688
+ if (expanded.length !== 1) throw new PortableObjectRejection("it does not have exactly one top-level node");
689
+ const id = expanded[0]["@id"];
690
+ if (id == null) return;
691
+ if (typeof id !== "string") throw new PortableObjectRejection("it has an invalid @id");
692
+ let actualId;
693
+ try {
694
+ const portable = require_url.fromCompatibleEf61Id(id);
695
+ actualId = require_url.canonicalizePortableUri(portable == null ? id : require_url.formatIri(portable));
696
+ } catch (error) {
697
+ if (!(error instanceof TypeError)) throw error;
698
+ actualId = null;
699
+ }
700
+ if (actualId !== expectedId) throw new PortableObjectRejection("its @id ({objectId}) does not match the requested portable ID", { objectId: id });
701
+ }
702
+ //#endregion
703
+ exports.PortableObjectRejectedError = PortableObjectRejectedError;
704
+ exports.copyPortableProvenance = copyPortableProvenance;
705
+ exports.createSnapshotContextLoader = createSnapshotContextLoader;
706
+ exports.dereferencePortableIri = dereferencePortableIri;
707
+ exports.getPortableGatewayCandidates = getPortableGatewayCandidates;
708
+ exports.getPortableResponseClaim = getPortableResponseClaim;
709
+ exports.getReferrerGateways = getReferrerGateways;
710
+ exports.isCompatibleEf61Iri = isCompatibleEf61Iri;
711
+ exports.isInPortableChain = isInPortableChain;
712
+ exports.isPortableIri = isPortableIri;
713
+ exports.isPortableMode = isPortableMode;
714
+ exports.isUnsecuredPortableObject = isUnsecuredPortableObject;
715
+ exports.isUnverifiedPortableClaim = isUnverifiedPortableClaim;
716
+ exports.markUnverifiedPortableClaim = markUnverifiedPortableClaim;
717
+ exports.mustDereferencePortableObject = mustDereferencePortableObject;
718
+ exports.parseCompatibleEf61Reference = parseCompatibleEf61Reference;
719
+ exports.recordPortableReferrer = recordPortableReferrer;
720
+ exports.rejectMalformedCompatibleReference = rejectMalformedCompatibleReference;
721
+ exports.warnUnverifiableEmbeddedObject = warnUnverifiableEmbeddedObject;