@fedify/vocab-runtime 2.4.0-dev.2219 → 2.4.0-dev.2233
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 +2 -2
- package/dist/internal/jsonld-cache.cjs +1 -1
- package/dist/internal/jsonld-cache.js +1 -1
- package/dist/internal/portable-dereference.cjs +4 -13
- package/dist/internal/portable-dereference.js +2 -11
- package/dist/mod.cjs +5 -2
- package/dist/mod.d.cts +95 -1
- package/dist/mod.d.ts +95 -1
- package/dist/mod.js +3 -3
- package/dist/tests/{body-BzhhuMI0.mjs → body-BEYUpKvX.mjs} +1 -1
- package/dist/tests/{body-GaHFfkaq.cjs → body-DQKTFMsH.cjs} +1 -1
- package/dist/tests/body.test.cjs +1 -1
- package/dist/tests/body.test.mjs +1 -1
- package/dist/tests/decimal.test.cjs +3 -3
- package/dist/tests/decimal.test.mjs +3 -3
- package/dist/tests/{docloader-sGr7kARm.cjs → docloader-DHCYdBXf.cjs} +3 -3
- package/dist/tests/{docloader-DiRcdiPp.mjs → docloader-ra1lbBfP.mjs} +3 -3
- package/dist/tests/docloader.test.cjs +3 -3
- package/dist/tests/docloader.test.mjs +3 -3
- package/dist/tests/internal/portable-dereference.test.cjs +16 -15
- package/dist/tests/internal/portable-dereference.test.mjs +14 -13
- package/dist/tests/{jsonld-cache-DUmuWRwp.mjs → jsonld-cache-BKrsWPCQ.mjs} +1 -1
- package/dist/tests/{jsonld-cache-BQMn_ILF.cjs → jsonld-cache-BTgizq9A.cjs} +1 -1
- package/dist/tests/jsonld-cache.test.cjs +2 -2
- package/dist/tests/jsonld-cache.test.mjs +2 -2
- package/dist/tests/{portable-media-BERS_tX_.mjs → portable-media-1NjUQari.mjs} +3 -3
- package/dist/tests/{portable-media-C_Q9GmqU.cjs → portable-media-DHaAEpaQ.cjs} +3 -3
- package/dist/tests/portable-media.test.cjs +1 -1
- package/dist/tests/portable-media.test.mjs +1 -1
- package/dist/tests/{request-qq1N2cl6.cjs → request-BQlkB8du.cjs} +1 -1
- package/dist/tests/{request-CWTG9_Xx.mjs → request-D3elVRyB.mjs} +1 -1
- package/dist/tests/request.test.cjs +1 -1
- package/dist/tests/request.test.mjs +1 -1
- package/dist/tests/{url-DfS-cvwr.mjs → url-Cdu4ASgJ.mjs} +165 -3
- package/dist/tests/{url-B8xyTwP-.cjs → url-CtHyzMR4.cjs} +194 -2
- package/dist/tests/url.test.cjs +133 -3
- package/dist/tests/url.test.mjs +133 -3
- package/dist/{url-DDVweCXr.cjs → url-BHfQuRdy.cjs} +194 -2
- package/dist/{url-Be3moMor.js → url-DqqDZY9i.js} +165 -3
- package/package.json +1 -1
- package/src/internal/portable-dereference.test.ts +16 -1
- package/src/internal/portable-dereference.ts +3 -20
- package/src/mod.ts +3 -0
- package/src/url.test.ts +311 -1
- package/src/url.ts +236 -8
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { t as preloadedContexts } from "../contexts-CIKsin4e.mjs";
|
|
2
|
-
import {
|
|
3
|
-
import { n as createScopedContextLoader, o as registerDocumentLoaderWrapper, s as unwrapReleasedDocumentLoader } from "../jsonld-cache-
|
|
2
|
+
import { S as withGatewayHints, _ as parseIri, h as parseGatewayOrigin, o as formatIri, t as GATEWAY_HINT_PARAMETER } from "../url-Cdu4ASgJ.mjs";
|
|
3
|
+
import { n as createScopedContextLoader, o as registerDocumentLoaderWrapper, s as unwrapReleasedDocumentLoader } from "../jsonld-cache-BKrsWPCQ.mjs";
|
|
4
4
|
import { deepStrictEqual, ok, throws } from "node:assert/strict";
|
|
5
5
|
import { test } from "node:test";
|
|
6
6
|
import { getLogger } from "@logtape/logtape";
|
|
@@ -17,7 +17,6 @@ const logger = getLogger([
|
|
|
17
17
|
* a single accessor call from fanning out to many servers.
|
|
18
18
|
*/
|
|
19
19
|
const MAX_GATEWAY_HINTS = 5;
|
|
20
|
-
const LOCATION_HINT_PARAMETER = "@gateway";
|
|
21
20
|
const BASELINE_CONTEXT_URLS = /* @__PURE__ */ new Set([
|
|
22
21
|
"https://w3id.org/identity/v1",
|
|
23
22
|
"https://www.w3.org/ns/activitystreams",
|
|
@@ -61,7 +60,7 @@ function getPortableGatewayCandidates(url, gateways) {
|
|
|
61
60
|
}
|
|
62
61
|
return candidates;
|
|
63
62
|
}
|
|
64
|
-
for (const hint of new URLSearchParams(url.search).getAll(
|
|
63
|
+
for (const hint of new URLSearchParams(url.search).getAll(GATEWAY_HINT_PARAMETER)) {
|
|
65
64
|
if (candidates.length >= MAX_GATEWAY_HINTS) break;
|
|
66
65
|
const parsed = parseGatewayOrigin(hint);
|
|
67
66
|
if (parsed == null) {
|
|
@@ -75,14 +74,6 @@ function getPortableGatewayCandidates(url, gateways) {
|
|
|
75
74
|
}
|
|
76
75
|
return candidates;
|
|
77
76
|
}
|
|
78
|
-
function parseGatewayOrigin(gateway) {
|
|
79
|
-
let url;
|
|
80
|
-
if (gateway instanceof URL) url = new URL(gateway.href);
|
|
81
|
-
else if (typeof gateway === "string" && URL.canParse(gateway)) url = new URL(gateway);
|
|
82
|
-
else return null;
|
|
83
|
-
if (url.protocol !== "http:" && url.protocol !== "https:" || url.href !== `${url.origin}/`) return null;
|
|
84
|
-
return url;
|
|
85
|
-
}
|
|
86
77
|
/**
|
|
87
78
|
* Creates a context loader that returns the same context documents for the
|
|
88
79
|
* whole dereference operation, so that the identity check, the proof
|
|
@@ -172,7 +163,7 @@ test("getPortableGatewayCandidates() uses explicit gateways", () => {
|
|
|
172
163
|
]) throws(() => getPortableGatewayCandidates(url, [gateway]), TypeError);
|
|
173
164
|
});
|
|
174
165
|
test("getPortableGatewayCandidates() reads @gateway hints", () => {
|
|
175
|
-
deepStrictEqual(hrefs(getPortableGatewayCandidates(parseIri("ap://did:key:z6Mkabc/actor?page=1&@gateway=https%3A%2F%2Fa.example&gateways=https%3A%2F%2Flegacy.example&@gateway=not%20a%20URL&@gateway=https%3A%2F%2Fb.example%2Fpath&%40gateway=https%3A%2F%2Fb.example&@gateway=https%3A%2F%2Fa.example%2F"))), ["https://a.example/", "https://b.example/"]);
|
|
166
|
+
deepStrictEqual(hrefs(getPortableGatewayCandidates(parseIri("ap://did:key:z6Mkabc/actor?page=1&@gateway=https%3A%2F%2Fa.example&gateways=https%3A%2F%2Flegacy.example&@gateway=not%20a%20URL&@gateway=https%3A%2F%2Fb.example%2Fpath&@gateway=https%3A%2F%2Fquery.example%2F%3F&@gateway=https%3A%2F%2Ffragment.example%2F%23&%40gateway=https%3A%2F%2Fb.example&@gateway=https%3A%2F%2Fa.example%2F"))), ["https://a.example/", "https://b.example/"]);
|
|
176
167
|
deepStrictEqual(getPortableGatewayCandidates(parseIri("ap://did:key:z6Mkabc/actor")), []);
|
|
177
168
|
const hints = Array.from({ length: 7 }, (_, i) => `@gateway=https%3A%2F%2Fg${i}.example`);
|
|
178
169
|
deepStrictEqual(hrefs(getPortableGatewayCandidates(parseIri(`ap://did:key:z6Mkabc/actor?${hints.join("&")}`))), [
|
|
@@ -183,6 +174,16 @@ test("getPortableGatewayCandidates() reads @gateway hints", () => {
|
|
|
183
174
|
4
|
|
184
175
|
].map((i) => `https://g${i}.example/`));
|
|
185
176
|
});
|
|
177
|
+
test("getPortableGatewayCandidates() reads hints from withGatewayHints()", () => {
|
|
178
|
+
const gateways = Array.from({ length: 7 }, (_, i) => `https://g${i}.example`);
|
|
179
|
+
deepStrictEqual(hrefs(getPortableGatewayCandidates(withGatewayHints("ap://did:key:z6Mkabc/actor?page=1", gateways))), [
|
|
180
|
+
0,
|
|
181
|
+
1,
|
|
182
|
+
2,
|
|
183
|
+
3,
|
|
184
|
+
4
|
|
185
|
+
].map((i) => `https://g${i}.example/`));
|
|
186
|
+
});
|
|
186
187
|
test("createSnapshotContextLoader() does not nest released snapshots", async () => {
|
|
187
188
|
const calls = [];
|
|
188
189
|
const base = (url) => {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { d as haveSameIriOrigin, o as formatIri, u as haveSameFe34Origin } from "./url-Cdu4ASgJ.mjs";
|
|
2
2
|
import jsonld from "jsonld/dist/jsonld.esm.js";
|
|
3
3
|
//#region src/jsonld.ts
|
|
4
4
|
var jsonld_default = jsonld;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
const require_rolldown_runtime = require("./rolldown-runtime-emK7D4bc.cjs");
|
|
2
|
-
const require_url = require("./url-
|
|
2
|
+
const require_url = require("./url-CtHyzMR4.cjs");
|
|
3
3
|
let jsonld_dist_jsonld_esm_js = require("jsonld/dist/jsonld.esm.js");
|
|
4
4
|
jsonld_dist_jsonld_esm_js = require_rolldown_runtime.__toESM(jsonld_dist_jsonld_esm_js, 1);
|
|
5
5
|
//#region src/jsonld.ts
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
const require_url = require("./url-
|
|
2
|
-
const require_jsonld_cache = require("./jsonld-cache-
|
|
1
|
+
const require_url = require("./url-CtHyzMR4.cjs");
|
|
2
|
+
const require_jsonld_cache = require("./jsonld-cache-BTgizq9A.cjs");
|
|
3
3
|
let node_test = require("node:test");
|
|
4
4
|
let node_assert = require("node:assert");
|
|
5
5
|
//#region src/jsonld-cache.test.ts
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { a as normalizeJsonLdIris, c as jsonld_default, i as isTrustedIriOrigin, r as getJsonLdContext, t as compactJsonLdCache } from "./jsonld-cache-
|
|
1
|
+
import { _ as parseIri } from "./url-Cdu4ASgJ.mjs";
|
|
2
|
+
import { a as normalizeJsonLdIris, c as jsonld_default, i as isTrustedIriOrigin, r as getJsonLdContext, t as compactJsonLdCache } from "./jsonld-cache-BKrsWPCQ.mjs";
|
|
3
3
|
import { test } from "node:test";
|
|
4
4
|
import { deepStrictEqual, ok, strictEqual } from "node:assert";
|
|
5
5
|
//#region src/jsonld-cache.test.ts
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { r as getUserAgent } from "./request-
|
|
2
|
-
import { r as readBoundedBytes } from "./body-
|
|
3
|
-
import {
|
|
1
|
+
import { r as getUserAgent } from "./request-D3elVRyB.mjs";
|
|
2
|
+
import { r as readBoundedBytes } from "./body-BEYUpKvX.mjs";
|
|
3
|
+
import { g as parseGatewayUrl, x as validatePublicUrl } from "./url-Cdu4ASgJ.mjs";
|
|
4
4
|
import { a as verifyDigestMultibase, i as parseHashlink, r as parseDigestMultibase } from "./digest-COC7xDiQ.mjs";
|
|
5
5
|
//#region src/portable-media.ts
|
|
6
6
|
function resolveTimeout(value, fallback) {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
const require_request = require("./request-
|
|
2
|
-
const require_body = require("./body-
|
|
3
|
-
const require_url = require("./url-
|
|
1
|
+
const require_request = require("./request-BQlkB8du.cjs");
|
|
2
|
+
const require_body = require("./body-DQKTFMsH.cjs");
|
|
3
|
+
const require_url = require("./url-CtHyzMR4.cjs");
|
|
4
4
|
const require_digest = require("./digest-3FeH2Y-Q.cjs");
|
|
5
5
|
//#region src/portable-media.ts
|
|
6
6
|
function resolveTimeout(value, fallback) {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
const require_digest = require("./digest-3FeH2Y-Q.cjs");
|
|
2
|
-
const require_portable_media = require("./portable-media-
|
|
2
|
+
const require_portable_media = require("./portable-media-DHaAEpaQ.cjs");
|
|
3
3
|
let node_assert_strict = require("node:assert/strict");
|
|
4
4
|
let node_test = require("node:test");
|
|
5
5
|
let node_http = require("node:http");
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { n as createHashlink, t as computeDigestMultibase } from "./digest-COC7xDiQ.mjs";
|
|
2
|
-
import { t as fetchPortableMedia } from "./portable-media-
|
|
2
|
+
import { t as fetchPortableMedia } from "./portable-media-1NjUQari.mjs";
|
|
3
3
|
import { deepStrictEqual, equal, rejects } from "node:assert/strict";
|
|
4
4
|
import { test } from "node:test";
|
|
5
5
|
import { createServer } from "node:http";
|
|
@@ -3,7 +3,7 @@ let node_process = require("node:process");
|
|
|
3
3
|
node_process = require_rolldown_runtime.__toESM(node_process, 1);
|
|
4
4
|
//#region deno.json
|
|
5
5
|
var name = "@fedify/vocab-runtime";
|
|
6
|
-
var version = "2.4.0-dev.
|
|
6
|
+
var version = "2.4.0-dev.2233+abb4a6b2";
|
|
7
7
|
//#endregion
|
|
8
8
|
//#region src/request.ts
|
|
9
9
|
/**
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
const require_rolldown_runtime = require("./rolldown-runtime-emK7D4bc.cjs");
|
|
2
|
-
const require_request = require("./request-
|
|
2
|
+
const require_request = require("./request-BQlkB8du.cjs");
|
|
3
3
|
let node_test = require("node:test");
|
|
4
4
|
let node_process = require("node:process");
|
|
5
5
|
node_process = require_rolldown_runtime.__toESM(node_process, 1);
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { n as createActivityPubRequest, o as version, r as getUserAgent } from "./request-
|
|
1
|
+
import { n as createActivityPubRequest, o as version, r as getUserAgent } from "./request-D3elVRyB.mjs";
|
|
2
2
|
import { test } from "node:test";
|
|
3
3
|
import process from "node:process";
|
|
4
4
|
import { deepStrictEqual } from "node:assert";
|
|
@@ -218,9 +218,10 @@ function parseAtUri(uri) {
|
|
|
218
218
|
}
|
|
219
219
|
/**
|
|
220
220
|
* Checks whether the URL is an FEP-ef61 gateway base URI.
|
|
221
|
+
* @since 2.4.0
|
|
221
222
|
*/
|
|
222
223
|
function isGatewayUrl(url) {
|
|
223
|
-
return (url.protocol === "http:" || url.protocol === "https:") && url.
|
|
224
|
+
return (url.protocol === "http:" || url.protocol === "https:") && url.href === `${url.origin}/`;
|
|
224
225
|
}
|
|
225
226
|
/**
|
|
226
227
|
* Parses and validates an FEP-ef61 gateway base URI.
|
|
@@ -228,6 +229,7 @@ function isGatewayUrl(url) {
|
|
|
228
229
|
* with no credentials, path, query, or fragment. In the
|
|
229
230
|
* latter case, the message starts with
|
|
230
231
|
* `Invalid FEP-ef61 gateway:`.
|
|
232
|
+
* @since 2.4.0
|
|
231
233
|
*/
|
|
232
234
|
function parseGatewayUrl(url) {
|
|
233
235
|
const parsed = parseIri(url);
|
|
@@ -355,7 +357,7 @@ function toCompatibleEf61Id(portableId, gateway) {
|
|
|
355
357
|
}
|
|
356
358
|
function parseCompatibleEf61Gateway(gateway) {
|
|
357
359
|
const url = gateway instanceof URL ? gateway : typeof gateway === "string" && URL.canParse(gateway) ? new URL(gateway) : null;
|
|
358
|
-
if (url == null || url
|
|
360
|
+
if (url == null || !isGatewayUrl(url)) throw new TypeError("FEP-ef61 gateways for compatible identifiers must be HTTP(S) origins with no credentials, path, query, or fragment.");
|
|
359
361
|
return url;
|
|
360
362
|
}
|
|
361
363
|
function getRawPortableIri(portableId) {
|
|
@@ -381,6 +383,166 @@ function isLocationHint(pair) {
|
|
|
381
383
|
}
|
|
382
384
|
}
|
|
383
385
|
/**
|
|
386
|
+
* The name of the FEP-ef61 location hint query parameter.
|
|
387
|
+
* @internal
|
|
388
|
+
*/
|
|
389
|
+
const GATEWAY_HINT_PARAMETER = "@gateway";
|
|
390
|
+
/**
|
|
391
|
+
* Parses an FEP-ef61 gateway, which has to be an HTTP(S) origin with no
|
|
392
|
+
* credentials, path, query, or fragment.
|
|
393
|
+
* @returns The gateway, or `null` if it is not a valid gateway.
|
|
394
|
+
* @internal
|
|
395
|
+
*/
|
|
396
|
+
function parseGatewayOrigin(gateway) {
|
|
397
|
+
let url;
|
|
398
|
+
if (gateway instanceof URL) url = new URL(gateway.href);
|
|
399
|
+
else if (typeof gateway === "string" && URL.canParse(gateway)) url = new URL(gateway);
|
|
400
|
+
else return null;
|
|
401
|
+
return isGatewayUrl(url) ? url : null;
|
|
402
|
+
}
|
|
403
|
+
/**
|
|
404
|
+
* Returns a copy of an [FEP-ef61] portable ActivityPub URI with `@gateway`
|
|
405
|
+
* location hints for the given gateways, which tell consumers where they can
|
|
406
|
+
* retrieve the object. Put hints on *references* to portable actors, e.g.,
|
|
407
|
+
* in `actor`, `attributedTo`, `to`, or `cc`, when constructing an object, as
|
|
408
|
+
* FEP-ef61 recommends:
|
|
409
|
+
*
|
|
410
|
+
* ~~~~ typescript
|
|
411
|
+
* withGatewayHints("ap://did:key:z6Mk.../actor", [
|
|
412
|
+
* "https://server1.example",
|
|
413
|
+
* "https://server2.example",
|
|
414
|
+
* ]);
|
|
415
|
+
* // ap+ef61://did:key:z6Mk.../actor?@gateway=https%3A%2F%2Fserver1.example&@gateway=https%3A%2F%2Fserver2.example
|
|
416
|
+
* ~~~~
|
|
417
|
+
*
|
|
418
|
+
* Do not put hints on an object's own `id`. Hints do not change the
|
|
419
|
+
* identity of a portable URI, since FEP-ef61 drops the query when comparing
|
|
420
|
+
* portable URIs, but implementations that do not canonicalize portable URIs
|
|
421
|
+
* would take a hinted ID for another object. Add hints before signing the
|
|
422
|
+
* object, since its Object Integrity Proof covers its references too.
|
|
423
|
+
*
|
|
424
|
+
* The hints that the URI already has, including the legacy `gateways`
|
|
425
|
+
* parameter, are replaced. Each gateway becomes a `@gateway` query
|
|
426
|
+
* parameter whose value is its URI-encoded origin, e.g.,
|
|
427
|
+
* `@gateway=https%3A%2F%2Fserver1.example`, in the given order after the
|
|
428
|
+
* other query parameters. Duplicate gateways are dropped, and an empty list
|
|
429
|
+
* removes the hints as {@link withoutGatewayHints} does. The other query
|
|
430
|
+
* parameters, their order, and the fragment are kept, but percent-encoding
|
|
431
|
+
* is normalized the same way as {@link canonicalizePortableUri} does it.
|
|
432
|
+
*
|
|
433
|
+
* Fedify follows at most five hints when dereferencing a portable URI
|
|
434
|
+
* (three for the key ID of an HTTP Signature), so list the preferred
|
|
435
|
+
* gateways first; there is no limit on the number of hints added here.
|
|
436
|
+
*
|
|
437
|
+
* [FEP-ef61]: https://w3id.org/fep/ef61
|
|
438
|
+
*
|
|
439
|
+
* @param portableId The `ap:` or `ap+ef61:` URI. Pass the raw string rather
|
|
440
|
+
* than a `URL` if its path may have `.` or `..` segments,
|
|
441
|
+
* because the `URL` class resolves them.
|
|
442
|
+
* @param gateways The gateways, e.g., the `gateways` of the actor that the
|
|
443
|
+
* URI refers to. Each has to be an HTTP(S) origin with no
|
|
444
|
+
* credentials, path, query, or fragment.
|
|
445
|
+
* @returns The portable URI with the hints, in the same internal `URL` form
|
|
446
|
+
* as {@link parseIri} returns. Use {@link formatIri} to get its
|
|
447
|
+
* canonical string.
|
|
448
|
+
* @throws {TypeError} If the portable ID is not a valid `ap:` or `ap+ef61:`
|
|
449
|
+
* URI, e.g., it is a compatible identifier, which must
|
|
450
|
+
* not have location hints; if its path has `.` or `..`
|
|
451
|
+
* segments, which the `URL` class cannot represent; or
|
|
452
|
+
* if a gateway is invalid.
|
|
453
|
+
* @since 2.4.0
|
|
454
|
+
*/
|
|
455
|
+
function withGatewayHints(portableId, gateways) {
|
|
456
|
+
const parts = splitPortableIri(portableId);
|
|
457
|
+
if (typeof gateways === "string") throw new TypeError("The gateways must be an iterable of gateways, not a string.");
|
|
458
|
+
const hints = [];
|
|
459
|
+
const seen = /* @__PURE__ */ new Set();
|
|
460
|
+
for (const gateway of gateways) {
|
|
461
|
+
const url = parseGatewayOrigin(gateway);
|
|
462
|
+
if (url == null) throw new TypeError("FEP-ef61 gateways must be HTTP(S) origins with no credentials, path, query, or fragment: " + String(gateway));
|
|
463
|
+
if (seen.has(url.href)) continue;
|
|
464
|
+
seen.add(url.href);
|
|
465
|
+
hints.push(url);
|
|
466
|
+
}
|
|
467
|
+
return replaceGatewayHints(parts, hints);
|
|
468
|
+
}
|
|
469
|
+
/**
|
|
470
|
+
* Returns a copy of an [FEP-ef61] portable ActivityPub URI without its
|
|
471
|
+
* location hints, i.e., `@gateway` query parameters and the legacy
|
|
472
|
+
* `gateways` parameter. The other query parameters, their order, and the
|
|
473
|
+
* fragment are kept, but percent-encoding is normalized the same way as
|
|
474
|
+
* {@link canonicalizePortableUri} does it.
|
|
475
|
+
*
|
|
476
|
+
* [FEP-ef61]: https://w3id.org/fep/ef61
|
|
477
|
+
*
|
|
478
|
+
* @param portableId The `ap:` or `ap+ef61:` URI. Pass the raw string rather
|
|
479
|
+
* than a `URL` if its path may have `.` or `..` segments,
|
|
480
|
+
* because the `URL` class resolves them.
|
|
481
|
+
* @returns The portable URI without the hints, in the same internal `URL`
|
|
482
|
+
* form as {@link parseIri} returns.
|
|
483
|
+
* @throws {TypeError} If the portable ID is not a valid `ap:` or `ap+ef61:`
|
|
484
|
+
* URI, or if its path has `.` or `..` segments, which
|
|
485
|
+
* the `URL` class cannot represent.
|
|
486
|
+
* @since 2.4.0
|
|
487
|
+
*/
|
|
488
|
+
function withoutGatewayHints(portableId) {
|
|
489
|
+
return replaceGatewayHints(splitPortableIri(portableId), []);
|
|
490
|
+
}
|
|
491
|
+
/**
|
|
492
|
+
* Gets the gateways in the `@gateway` location hints of an [FEP-ef61]
|
|
493
|
+
* portable ActivityPub URI, in order. Hints that are not valid gateways,
|
|
494
|
+
* i.e., HTTP(S) origins with no credentials, path, query, or fragment, are
|
|
495
|
+
* skipped, and so are duplicates. The legacy `gateways` parameter is not
|
|
496
|
+
* read.
|
|
497
|
+
*
|
|
498
|
+
* Unlike Fedify's dereferencing, which follows at most five hints, this
|
|
499
|
+
* returns all of them.
|
|
500
|
+
*
|
|
501
|
+
* [FEP-ef61]: https://w3id.org/fep/ef61
|
|
502
|
+
*
|
|
503
|
+
* @param portableId The `ap:` or `ap+ef61:` URI.
|
|
504
|
+
* @returns The gateways, e.g., `https://server1.example/`.
|
|
505
|
+
* @throws {TypeError} If the portable ID is not a valid `ap:` or `ap+ef61:`
|
|
506
|
+
* URI.
|
|
507
|
+
* @since 2.4.0
|
|
508
|
+
*/
|
|
509
|
+
function getGatewayHints(portableId) {
|
|
510
|
+
const { query } = splitPortableIri(portableId);
|
|
511
|
+
return query == null ? [] : parseGatewayHints(query);
|
|
512
|
+
}
|
|
513
|
+
function splitPortableIri(portableId) {
|
|
514
|
+
const raw = getRawPortableIri(portableId);
|
|
515
|
+
const match = raw.match(PORTABLE_IRI_PATTERN);
|
|
516
|
+
const parsed = parsePortableIri(raw);
|
|
517
|
+
if (match == null || parsed == null) throw new TypeError("Invalid portable ActivityPub IRI.");
|
|
518
|
+
return {
|
|
519
|
+
raw,
|
|
520
|
+
parsed,
|
|
521
|
+
path: normalizePortableComponent(match[3]),
|
|
522
|
+
query: match[4] == null ? null : normalizePortableComponent(match[4].slice(1)),
|
|
523
|
+
fragment: match[5] == null ? "" : normalizePortableComponent(match[5])
|
|
524
|
+
};
|
|
525
|
+
}
|
|
526
|
+
function parseGatewayHints(query) {
|
|
527
|
+
const hints = [];
|
|
528
|
+
const seen = /* @__PURE__ */ new Set();
|
|
529
|
+
for (const hint of new URLSearchParams(`?${query}`).getAll(GATEWAY_HINT_PARAMETER)) {
|
|
530
|
+
const url = parseGatewayOrigin(hint);
|
|
531
|
+
if (url == null || seen.has(url.href)) continue;
|
|
532
|
+
seen.add(url.href);
|
|
533
|
+
hints.push(url);
|
|
534
|
+
}
|
|
535
|
+
return hints;
|
|
536
|
+
}
|
|
537
|
+
function replaceGatewayHints(parts, hints) {
|
|
538
|
+
const pairs = parts.query == null ? [] : parts.query.split("&").filter((pair) => pair !== "" && !isLocationHint(pair));
|
|
539
|
+
for (const hint of hints) pairs.push(`${GATEWAY_HINT_PARAMETER}=${encodeURIComponent(hint.origin)}`);
|
|
540
|
+
const query = pairs.length < 1 ? "" : `?${pairs.join("&")}`;
|
|
541
|
+
const result = parsePortableIri(`ap+ef61://${parts.parsed.host}${parts.path}${query}${parts.fragment}`);
|
|
542
|
+
if (result == null || canonicalizePortableUri(result.href) !== canonicalizePortableUri(parts.raw) || parseGatewayHints(result.search.slice(1)).map((url) => url.href).join(" ") !== hints.map((url) => url.href).join(" ")) throw new TypeError("The portable ActivityPub IRI cannot be represented as a URL without changing the object it refers to.");
|
|
543
|
+
return result;
|
|
544
|
+
}
|
|
545
|
+
/**
|
|
384
546
|
* Validates a URL to prevent SSRF attacks.
|
|
385
547
|
*/
|
|
386
548
|
async function validatePublicUrl(url) {
|
|
@@ -576,4 +738,4 @@ function matchesIPv6Prefix(address, prefixWords, prefixLength) {
|
|
|
576
738
|
return true;
|
|
577
739
|
}
|
|
578
740
|
//#endregion
|
|
579
|
-
export {
|
|
741
|
+
export { withoutGatewayHints as C, withGatewayHints as S, parseIri as _, expandIPv6Address as a, validateLookupAddresses as b, getFe34Origin as c, haveSameIriOrigin as d, isGatewayUrl as f, parseGatewayUrl as g, parseGatewayOrigin as h, canonicalizePortableUri as i, getGatewayHints as l, isValidPublicIPv6Address as m, UrlError as n, formatIri as o, isValidPublicIPv4Address as p, arePortableUrisEqual as r, fromCompatibleEf61Id as s, GATEWAY_HINT_PARAMETER as t, haveSameFe34Origin as u, parseJsonLdId as v, validatePublicUrl as x, toCompatibleEf61Id as y };
|
|
@@ -220,9 +220,10 @@ function parseAtUri(uri) {
|
|
|
220
220
|
}
|
|
221
221
|
/**
|
|
222
222
|
* Checks whether the URL is an FEP-ef61 gateway base URI.
|
|
223
|
+
* @since 2.4.0
|
|
223
224
|
*/
|
|
224
225
|
function isGatewayUrl(url) {
|
|
225
|
-
return (url.protocol === "http:" || url.protocol === "https:") && url.
|
|
226
|
+
return (url.protocol === "http:" || url.protocol === "https:") && url.href === `${url.origin}/`;
|
|
226
227
|
}
|
|
227
228
|
/**
|
|
228
229
|
* Parses and validates an FEP-ef61 gateway base URI.
|
|
@@ -230,6 +231,7 @@ function isGatewayUrl(url) {
|
|
|
230
231
|
* with no credentials, path, query, or fragment. In the
|
|
231
232
|
* latter case, the message starts with
|
|
232
233
|
* `Invalid FEP-ef61 gateway:`.
|
|
234
|
+
* @since 2.4.0
|
|
233
235
|
*/
|
|
234
236
|
function parseGatewayUrl(url) {
|
|
235
237
|
const parsed = parseIri(url);
|
|
@@ -357,7 +359,7 @@ function toCompatibleEf61Id(portableId, gateway) {
|
|
|
357
359
|
}
|
|
358
360
|
function parseCompatibleEf61Gateway(gateway) {
|
|
359
361
|
const url = gateway instanceof URL ? gateway : typeof gateway === "string" && URL.canParse(gateway) ? new URL(gateway) : null;
|
|
360
|
-
if (url == null || url
|
|
362
|
+
if (url == null || !isGatewayUrl(url)) throw new TypeError("FEP-ef61 gateways for compatible identifiers must be HTTP(S) origins with no credentials, path, query, or fragment.");
|
|
361
363
|
return url;
|
|
362
364
|
}
|
|
363
365
|
function getRawPortableIri(portableId) {
|
|
@@ -383,6 +385,166 @@ function isLocationHint(pair) {
|
|
|
383
385
|
}
|
|
384
386
|
}
|
|
385
387
|
/**
|
|
388
|
+
* The name of the FEP-ef61 location hint query parameter.
|
|
389
|
+
* @internal
|
|
390
|
+
*/
|
|
391
|
+
const GATEWAY_HINT_PARAMETER = "@gateway";
|
|
392
|
+
/**
|
|
393
|
+
* Parses an FEP-ef61 gateway, which has to be an HTTP(S) origin with no
|
|
394
|
+
* credentials, path, query, or fragment.
|
|
395
|
+
* @returns The gateway, or `null` if it is not a valid gateway.
|
|
396
|
+
* @internal
|
|
397
|
+
*/
|
|
398
|
+
function parseGatewayOrigin(gateway) {
|
|
399
|
+
let url;
|
|
400
|
+
if (gateway instanceof URL) url = new URL(gateway.href);
|
|
401
|
+
else if (typeof gateway === "string" && URL.canParse(gateway)) url = new URL(gateway);
|
|
402
|
+
else return null;
|
|
403
|
+
return isGatewayUrl(url) ? url : null;
|
|
404
|
+
}
|
|
405
|
+
/**
|
|
406
|
+
* Returns a copy of an [FEP-ef61] portable ActivityPub URI with `@gateway`
|
|
407
|
+
* location hints for the given gateways, which tell consumers where they can
|
|
408
|
+
* retrieve the object. Put hints on *references* to portable actors, e.g.,
|
|
409
|
+
* in `actor`, `attributedTo`, `to`, or `cc`, when constructing an object, as
|
|
410
|
+
* FEP-ef61 recommends:
|
|
411
|
+
*
|
|
412
|
+
* ~~~~ typescript
|
|
413
|
+
* withGatewayHints("ap://did:key:z6Mk.../actor", [
|
|
414
|
+
* "https://server1.example",
|
|
415
|
+
* "https://server2.example",
|
|
416
|
+
* ]);
|
|
417
|
+
* // ap+ef61://did:key:z6Mk.../actor?@gateway=https%3A%2F%2Fserver1.example&@gateway=https%3A%2F%2Fserver2.example
|
|
418
|
+
* ~~~~
|
|
419
|
+
*
|
|
420
|
+
* Do not put hints on an object's own `id`. Hints do not change the
|
|
421
|
+
* identity of a portable URI, since FEP-ef61 drops the query when comparing
|
|
422
|
+
* portable URIs, but implementations that do not canonicalize portable URIs
|
|
423
|
+
* would take a hinted ID for another object. Add hints before signing the
|
|
424
|
+
* object, since its Object Integrity Proof covers its references too.
|
|
425
|
+
*
|
|
426
|
+
* The hints that the URI already has, including the legacy `gateways`
|
|
427
|
+
* parameter, are replaced. Each gateway becomes a `@gateway` query
|
|
428
|
+
* parameter whose value is its URI-encoded origin, e.g.,
|
|
429
|
+
* `@gateway=https%3A%2F%2Fserver1.example`, in the given order after the
|
|
430
|
+
* other query parameters. Duplicate gateways are dropped, and an empty list
|
|
431
|
+
* removes the hints as {@link withoutGatewayHints} does. The other query
|
|
432
|
+
* parameters, their order, and the fragment are kept, but percent-encoding
|
|
433
|
+
* is normalized the same way as {@link canonicalizePortableUri} does it.
|
|
434
|
+
*
|
|
435
|
+
* Fedify follows at most five hints when dereferencing a portable URI
|
|
436
|
+
* (three for the key ID of an HTTP Signature), so list the preferred
|
|
437
|
+
* gateways first; there is no limit on the number of hints added here.
|
|
438
|
+
*
|
|
439
|
+
* [FEP-ef61]: https://w3id.org/fep/ef61
|
|
440
|
+
*
|
|
441
|
+
* @param portableId The `ap:` or `ap+ef61:` URI. Pass the raw string rather
|
|
442
|
+
* than a `URL` if its path may have `.` or `..` segments,
|
|
443
|
+
* because the `URL` class resolves them.
|
|
444
|
+
* @param gateways The gateways, e.g., the `gateways` of the actor that the
|
|
445
|
+
* URI refers to. Each has to be an HTTP(S) origin with no
|
|
446
|
+
* credentials, path, query, or fragment.
|
|
447
|
+
* @returns The portable URI with the hints, in the same internal `URL` form
|
|
448
|
+
* as {@link parseIri} returns. Use {@link formatIri} to get its
|
|
449
|
+
* canonical string.
|
|
450
|
+
* @throws {TypeError} If the portable ID is not a valid `ap:` or `ap+ef61:`
|
|
451
|
+
* URI, e.g., it is a compatible identifier, which must
|
|
452
|
+
* not have location hints; if its path has `.` or `..`
|
|
453
|
+
* segments, which the `URL` class cannot represent; or
|
|
454
|
+
* if a gateway is invalid.
|
|
455
|
+
* @since 2.4.0
|
|
456
|
+
*/
|
|
457
|
+
function withGatewayHints(portableId, gateways) {
|
|
458
|
+
const parts = splitPortableIri(portableId);
|
|
459
|
+
if (typeof gateways === "string") throw new TypeError("The gateways must be an iterable of gateways, not a string.");
|
|
460
|
+
const hints = [];
|
|
461
|
+
const seen = /* @__PURE__ */ new Set();
|
|
462
|
+
for (const gateway of gateways) {
|
|
463
|
+
const url = parseGatewayOrigin(gateway);
|
|
464
|
+
if (url == null) throw new TypeError("FEP-ef61 gateways must be HTTP(S) origins with no credentials, path, query, or fragment: " + String(gateway));
|
|
465
|
+
if (seen.has(url.href)) continue;
|
|
466
|
+
seen.add(url.href);
|
|
467
|
+
hints.push(url);
|
|
468
|
+
}
|
|
469
|
+
return replaceGatewayHints(parts, hints);
|
|
470
|
+
}
|
|
471
|
+
/**
|
|
472
|
+
* Returns a copy of an [FEP-ef61] portable ActivityPub URI without its
|
|
473
|
+
* location hints, i.e., `@gateway` query parameters and the legacy
|
|
474
|
+
* `gateways` parameter. The other query parameters, their order, and the
|
|
475
|
+
* fragment are kept, but percent-encoding is normalized the same way as
|
|
476
|
+
* {@link canonicalizePortableUri} does it.
|
|
477
|
+
*
|
|
478
|
+
* [FEP-ef61]: https://w3id.org/fep/ef61
|
|
479
|
+
*
|
|
480
|
+
* @param portableId The `ap:` or `ap+ef61:` URI. Pass the raw string rather
|
|
481
|
+
* than a `URL` if its path may have `.` or `..` segments,
|
|
482
|
+
* because the `URL` class resolves them.
|
|
483
|
+
* @returns The portable URI without the hints, in the same internal `URL`
|
|
484
|
+
* form as {@link parseIri} returns.
|
|
485
|
+
* @throws {TypeError} If the portable ID is not a valid `ap:` or `ap+ef61:`
|
|
486
|
+
* URI, or if its path has `.` or `..` segments, which
|
|
487
|
+
* the `URL` class cannot represent.
|
|
488
|
+
* @since 2.4.0
|
|
489
|
+
*/
|
|
490
|
+
function withoutGatewayHints(portableId) {
|
|
491
|
+
return replaceGatewayHints(splitPortableIri(portableId), []);
|
|
492
|
+
}
|
|
493
|
+
/**
|
|
494
|
+
* Gets the gateways in the `@gateway` location hints of an [FEP-ef61]
|
|
495
|
+
* portable ActivityPub URI, in order. Hints that are not valid gateways,
|
|
496
|
+
* i.e., HTTP(S) origins with no credentials, path, query, or fragment, are
|
|
497
|
+
* skipped, and so are duplicates. The legacy `gateways` parameter is not
|
|
498
|
+
* read.
|
|
499
|
+
*
|
|
500
|
+
* Unlike Fedify's dereferencing, which follows at most five hints, this
|
|
501
|
+
* returns all of them.
|
|
502
|
+
*
|
|
503
|
+
* [FEP-ef61]: https://w3id.org/fep/ef61
|
|
504
|
+
*
|
|
505
|
+
* @param portableId The `ap:` or `ap+ef61:` URI.
|
|
506
|
+
* @returns The gateways, e.g., `https://server1.example/`.
|
|
507
|
+
* @throws {TypeError} If the portable ID is not a valid `ap:` or `ap+ef61:`
|
|
508
|
+
* URI.
|
|
509
|
+
* @since 2.4.0
|
|
510
|
+
*/
|
|
511
|
+
function getGatewayHints(portableId) {
|
|
512
|
+
const { query } = splitPortableIri(portableId);
|
|
513
|
+
return query == null ? [] : parseGatewayHints(query);
|
|
514
|
+
}
|
|
515
|
+
function splitPortableIri(portableId) {
|
|
516
|
+
const raw = getRawPortableIri(portableId);
|
|
517
|
+
const match = raw.match(PORTABLE_IRI_PATTERN);
|
|
518
|
+
const parsed = parsePortableIri(raw);
|
|
519
|
+
if (match == null || parsed == null) throw new TypeError("Invalid portable ActivityPub IRI.");
|
|
520
|
+
return {
|
|
521
|
+
raw,
|
|
522
|
+
parsed,
|
|
523
|
+
path: normalizePortableComponent(match[3]),
|
|
524
|
+
query: match[4] == null ? null : normalizePortableComponent(match[4].slice(1)),
|
|
525
|
+
fragment: match[5] == null ? "" : normalizePortableComponent(match[5])
|
|
526
|
+
};
|
|
527
|
+
}
|
|
528
|
+
function parseGatewayHints(query) {
|
|
529
|
+
const hints = [];
|
|
530
|
+
const seen = /* @__PURE__ */ new Set();
|
|
531
|
+
for (const hint of new URLSearchParams(`?${query}`).getAll(GATEWAY_HINT_PARAMETER)) {
|
|
532
|
+
const url = parseGatewayOrigin(hint);
|
|
533
|
+
if (url == null || seen.has(url.href)) continue;
|
|
534
|
+
seen.add(url.href);
|
|
535
|
+
hints.push(url);
|
|
536
|
+
}
|
|
537
|
+
return hints;
|
|
538
|
+
}
|
|
539
|
+
function replaceGatewayHints(parts, hints) {
|
|
540
|
+
const pairs = parts.query == null ? [] : parts.query.split("&").filter((pair) => pair !== "" && !isLocationHint(pair));
|
|
541
|
+
for (const hint of hints) pairs.push(`${GATEWAY_HINT_PARAMETER}=${encodeURIComponent(hint.origin)}`);
|
|
542
|
+
const query = pairs.length < 1 ? "" : `?${pairs.join("&")}`;
|
|
543
|
+
const result = parsePortableIri(`ap+ef61://${parts.parsed.host}${parts.path}${query}${parts.fragment}`);
|
|
544
|
+
if (result == null || canonicalizePortableUri(result.href) !== canonicalizePortableUri(parts.raw) || parseGatewayHints(result.search.slice(1)).map((url) => url.href).join(" ") !== hints.map((url) => url.href).join(" ")) throw new TypeError("The portable ActivityPub IRI cannot be represented as a URL without changing the object it refers to.");
|
|
545
|
+
return result;
|
|
546
|
+
}
|
|
547
|
+
/**
|
|
386
548
|
* Validates a URL to prevent SSRF attacks.
|
|
387
549
|
*/
|
|
388
550
|
async function validatePublicUrl(url) {
|
|
@@ -578,6 +740,12 @@ function matchesIPv6Prefix(address, prefixWords, prefixLength) {
|
|
|
578
740
|
return true;
|
|
579
741
|
}
|
|
580
742
|
//#endregion
|
|
743
|
+
Object.defineProperty(exports, "GATEWAY_HINT_PARAMETER", {
|
|
744
|
+
enumerable: true,
|
|
745
|
+
get: function() {
|
|
746
|
+
return GATEWAY_HINT_PARAMETER;
|
|
747
|
+
}
|
|
748
|
+
});
|
|
581
749
|
Object.defineProperty(exports, "UrlError", {
|
|
582
750
|
enumerable: true,
|
|
583
751
|
get: function() {
|
|
@@ -620,6 +788,12 @@ Object.defineProperty(exports, "getFe34Origin", {
|
|
|
620
788
|
return getFe34Origin;
|
|
621
789
|
}
|
|
622
790
|
});
|
|
791
|
+
Object.defineProperty(exports, "getGatewayHints", {
|
|
792
|
+
enumerable: true,
|
|
793
|
+
get: function() {
|
|
794
|
+
return getGatewayHints;
|
|
795
|
+
}
|
|
796
|
+
});
|
|
623
797
|
Object.defineProperty(exports, "haveSameFe34Origin", {
|
|
624
798
|
enumerable: true,
|
|
625
799
|
get: function() {
|
|
@@ -650,6 +824,12 @@ Object.defineProperty(exports, "isValidPublicIPv6Address", {
|
|
|
650
824
|
return isValidPublicIPv6Address;
|
|
651
825
|
}
|
|
652
826
|
});
|
|
827
|
+
Object.defineProperty(exports, "parseGatewayOrigin", {
|
|
828
|
+
enumerable: true,
|
|
829
|
+
get: function() {
|
|
830
|
+
return parseGatewayOrigin;
|
|
831
|
+
}
|
|
832
|
+
});
|
|
653
833
|
Object.defineProperty(exports, "parseGatewayUrl", {
|
|
654
834
|
enumerable: true,
|
|
655
835
|
get: function() {
|
|
@@ -686,3 +866,15 @@ Object.defineProperty(exports, "validatePublicUrl", {
|
|
|
686
866
|
return validatePublicUrl;
|
|
687
867
|
}
|
|
688
868
|
});
|
|
869
|
+
Object.defineProperty(exports, "withGatewayHints", {
|
|
870
|
+
enumerable: true,
|
|
871
|
+
get: function() {
|
|
872
|
+
return withGatewayHints;
|
|
873
|
+
}
|
|
874
|
+
});
|
|
875
|
+
Object.defineProperty(exports, "withoutGatewayHints", {
|
|
876
|
+
enumerable: true,
|
|
877
|
+
get: function() {
|
|
878
|
+
return withoutGatewayHints;
|
|
879
|
+
}
|
|
880
|
+
});
|