@fedify/vocab-runtime 2.4.0-dev.2233 → 2.4.0-dev.2245
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 +3 -6
- package/dist/internal/portable-dereference.js +3 -6
- package/dist/mod.cjs +2 -2
- package/dist/mod.d.cts +14 -3
- package/dist/mod.d.ts +14 -3
- package/dist/mod.js +2 -2
- package/dist/tests/{body-DQKTFMsH.cjs → body-C7LfsQJ3.cjs} +1 -1
- package/dist/tests/{body-BEYUpKvX.mjs → body-D50XSh7s.mjs} +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-ra1lbBfP.mjs → docloader-BdcRw8S9.mjs} +3 -3
- package/dist/tests/{docloader-DHCYdBXf.cjs → docloader-oUrWeeUz.cjs} +3 -3
- package/dist/tests/docloader.test.cjs +59 -65
- package/dist/tests/docloader.test.mjs +38 -44
- package/dist/tests/internal/portable-dereference.test.cjs +21 -149
- package/dist/tests/internal/portable-dereference.test.mjs +3 -131
- package/dist/tests/{jsonld-cache-BKrsWPCQ.mjs → jsonld-cache-CKfgtxPB.mjs} +1 -1
- package/dist/tests/{jsonld-cache-BTgizq9A.cjs → jsonld-cache-DzxT9IXH.cjs} +1 -1
- package/dist/tests/jsonld-cache.test.cjs +2 -2
- package/dist/tests/jsonld-cache.test.mjs +2 -2
- package/dist/tests/multibase/multibase.test.cjs +7 -7
- package/dist/tests/multibase/multibase.test.mjs +5 -5
- package/dist/tests/portable-dereference-C_cwGjz6.mjs +132 -0
- package/dist/tests/portable-dereference-CyFYTBnr.cjs +149 -0
- package/dist/tests/{portable-media-DHaAEpaQ.cjs → portable-media-Bj5FXpUr.cjs} +3 -3
- package/dist/tests/{portable-media-1NjUQari.mjs → portable-media-D4NJcqkI.mjs} +3 -3
- package/dist/tests/portable-media.test.cjs +1 -1
- package/dist/tests/portable-media.test.mjs +1 -1
- package/dist/tests/portable-workers.test.cjs +36 -0
- package/dist/tests/portable-workers.test.d.cts +2 -0
- package/dist/tests/portable-workers.test.d.mts +2 -0
- package/dist/tests/portable-workers.test.mjs +35 -0
- package/dist/tests/{request-BQlkB8du.cjs → request-CBCk-e0R.cjs} +1 -1
- package/dist/tests/{request-D3elVRyB.mjs → request-wKyc91L5.mjs} +1 -1
- package/dist/tests/request.test.cjs +1 -1
- package/dist/tests/request.test.mjs +1 -1
- package/dist/tests/{url-Cdu4ASgJ.mjs → url-CWC-NI3a.mjs} +63 -7
- package/dist/tests/{url-CtHyzMR4.cjs → url-DsMBGqCc.cjs} +63 -7
- package/dist/tests/url.test.cjs +74 -1
- package/dist/tests/url.test.mjs +74 -1
- package/dist/{url-BHfQuRdy.cjs → url-BJMXv2kE.cjs} +63 -7
- package/dist/{url-DqqDZY9i.js → url-CZm2-7ZO.js} +63 -7
- package/package.json +6 -3
- package/scripts/test-bun.mjs +17 -0
- package/src/docloader.test.ts +82 -74
- package/src/internal/portable-dereference.ts +2 -5
- package/src/multibase/multibase.test.ts +5 -5
- package/src/portable-workers.test.ts +81 -0
- package/src/url.test.ts +90 -0
- package/src/url.ts +115 -13
|
@@ -1,136 +1,8 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import { n as
|
|
1
|
+
import { S as withGatewayHints, _ as parseIri } from "../url-CWC-NI3a.mjs";
|
|
2
|
+
import { n as createScopedContextLoader } from "../jsonld-cache-CKfgtxPB.mjs";
|
|
3
|
+
import { n as getPortableGatewayCandidates, r as isPortableIri, t as createSnapshotContextLoader } from "../portable-dereference-C_cwGjz6.mjs";
|
|
4
4
|
import { deepStrictEqual, ok, throws } from "node:assert/strict";
|
|
5
5
|
import { test } from "node:test";
|
|
6
|
-
import { getLogger } from "@logtape/logtape";
|
|
7
|
-
import "@opentelemetry/api";
|
|
8
|
-
//#region src/internal/portable-dereference.ts
|
|
9
|
-
const logger = getLogger([
|
|
10
|
-
"fedify",
|
|
11
|
-
"vocab",
|
|
12
|
-
"gateway"
|
|
13
|
-
]);
|
|
14
|
-
/**
|
|
15
|
-
* The maximum number of `@gateway` location hints to try for one reference.
|
|
16
|
-
* Hints come from possibly untrusted documents, so they are bounded to keep
|
|
17
|
-
* a single accessor call from fanning out to many servers.
|
|
18
|
-
*/
|
|
19
|
-
const MAX_GATEWAY_HINTS = 5;
|
|
20
|
-
const BASELINE_CONTEXT_URLS = /* @__PURE__ */ new Set([
|
|
21
|
-
"https://w3id.org/identity/v1",
|
|
22
|
-
"https://www.w3.org/ns/activitystreams",
|
|
23
|
-
"https://w3id.org/security/v1",
|
|
24
|
-
"https://w3id.org/security/data-integrity/v1"
|
|
25
|
-
]);
|
|
26
|
-
/**
|
|
27
|
-
* Checks whether a URL is an FEP-ef61 portable ActivityPub IRI.
|
|
28
|
-
*
|
|
29
|
-
* @internal Technically exported for generated vocabulary classes, but not
|
|
30
|
-
* part of the public API contract. This is not considered public API for
|
|
31
|
-
* Semantic Versioning decisions.
|
|
32
|
-
*/
|
|
33
|
-
function isPortableIri(url) {
|
|
34
|
-
return url.protocol === "ap+ef61:" || url.protocol === "ap:";
|
|
35
|
-
}
|
|
36
|
-
/**
|
|
37
|
-
* Picks the ordered list of FEP-ef61 gateways to fetch a portable IRI from.
|
|
38
|
-
*
|
|
39
|
-
* If `gateways` is given, it is used as is (even when empty), and `@gateway`
|
|
40
|
-
* location hints in the IRI are ignored. Otherwise, up to
|
|
41
|
-
* {@link MAX_GATEWAY_HINTS} valid `@gateway` hints are used. Duplicate
|
|
42
|
-
* gateways are dropped in both cases.
|
|
43
|
-
*
|
|
44
|
-
* @throws {TypeError} If an explicit gateway is not an HTTP(S) origin.
|
|
45
|
-
* @internal
|
|
46
|
-
*/
|
|
47
|
-
function getPortableGatewayCandidates(url, gateways) {
|
|
48
|
-
const candidates = [];
|
|
49
|
-
const seen = /* @__PURE__ */ new Set();
|
|
50
|
-
const add = (gateway) => {
|
|
51
|
-
if (seen.has(gateway.href)) return;
|
|
52
|
-
seen.add(gateway.href);
|
|
53
|
-
candidates.push(gateway);
|
|
54
|
-
};
|
|
55
|
-
if (gateways != null) {
|
|
56
|
-
for (const gateway of gateways) {
|
|
57
|
-
const parsed = parseGatewayOrigin(gateway);
|
|
58
|
-
if (parsed == null) throw new TypeError("FEP-ef61 gateways must be HTTP(S) origins with no credentials, path, query, or fragment: " + String(gateway));
|
|
59
|
-
add(parsed);
|
|
60
|
-
}
|
|
61
|
-
return candidates;
|
|
62
|
-
}
|
|
63
|
-
for (const hint of new URLSearchParams(url.search).getAll(GATEWAY_HINT_PARAMETER)) {
|
|
64
|
-
if (candidates.length >= MAX_GATEWAY_HINTS) break;
|
|
65
|
-
const parsed = parseGatewayOrigin(hint);
|
|
66
|
-
if (parsed == null) {
|
|
67
|
-
logger.debug("Ignoring an invalid FEP-ef61 gateway hint {hint} in {url}.", {
|
|
68
|
-
hint,
|
|
69
|
-
url: formatIri(url)
|
|
70
|
-
});
|
|
71
|
-
continue;
|
|
72
|
-
}
|
|
73
|
-
add(parsed);
|
|
74
|
-
}
|
|
75
|
-
return candidates;
|
|
76
|
-
}
|
|
77
|
-
/**
|
|
78
|
-
* Creates a context loader that returns the same context documents for the
|
|
79
|
-
* whole dereference operation, so that the identity check, the proof
|
|
80
|
-
* verifier, and the parser interpret the fetched document identically even
|
|
81
|
-
* if the underlying loader is nondeterministic. Failed loads are not
|
|
82
|
-
* remembered, so a transient failure does not affect the next gateway.
|
|
83
|
-
*
|
|
84
|
-
* The parsed object keeps the loader for its own later dereferences, so
|
|
85
|
-
* `release()` turns it into a plain pass-through to the underlying loader
|
|
86
|
-
* once the operation is over.
|
|
87
|
-
*
|
|
88
|
-
* @internal Technically exported for generated vocabulary classes, but not
|
|
89
|
-
* part of the public API contract. This is not considered public API for
|
|
90
|
-
* Semantic Versioning decisions.
|
|
91
|
-
*/
|
|
92
|
-
function createSnapshotContextLoader(contextLoader, suppressError) {
|
|
93
|
-
contextLoader = unwrapReleasedDocumentLoader(contextLoader);
|
|
94
|
-
const cache = /* @__PURE__ */ new Map();
|
|
95
|
-
let released = false;
|
|
96
|
-
const release = () => {
|
|
97
|
-
released = true;
|
|
98
|
-
state.released = true;
|
|
99
|
-
cache.clear();
|
|
100
|
-
};
|
|
101
|
-
const loader = async (url, options) => {
|
|
102
|
-
if (released) return await contextLoader(url, options);
|
|
103
|
-
const key = URL.canParse(url) ? new URL(url).href : url;
|
|
104
|
-
if (BASELINE_CONTEXT_URLS.has(key)) return {
|
|
105
|
-
contextUrl: null,
|
|
106
|
-
document: structuredClone(preloadedContexts[key]),
|
|
107
|
-
documentUrl: key
|
|
108
|
-
};
|
|
109
|
-
let promise = cache.get(key);
|
|
110
|
-
if (promise == null) {
|
|
111
|
-
const loading = contextLoader(url, suppressError ? {
|
|
112
|
-
...options,
|
|
113
|
-
suppressError: true
|
|
114
|
-
} : options).then((document) => structuredClone(document));
|
|
115
|
-
promise = loading;
|
|
116
|
-
cache.set(key, loading);
|
|
117
|
-
loading.catch(() => {
|
|
118
|
-
if (cache.get(key) === loading) cache.delete(key);
|
|
119
|
-
});
|
|
120
|
-
}
|
|
121
|
-
return structuredClone(await promise);
|
|
122
|
-
};
|
|
123
|
-
const state = {
|
|
124
|
-
base: contextLoader,
|
|
125
|
-
released: false
|
|
126
|
-
};
|
|
127
|
-
registerDocumentLoaderWrapper(loader, state);
|
|
128
|
-
return {
|
|
129
|
-
loader,
|
|
130
|
-
release
|
|
131
|
-
};
|
|
132
|
-
}
|
|
133
|
-
//#endregion
|
|
134
6
|
//#region src/internal/portable-dereference.test.ts
|
|
135
7
|
const hrefs = (urls) => urls.map((url) => url.href);
|
|
136
8
|
test("isPortableIri()", () => {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { d as haveSameIriOrigin, o as formatIri, u as haveSameFe34Origin } from "./url-
|
|
1
|
+
import { d as haveSameIriOrigin, o as formatIri, u as haveSameFe34Origin } from "./url-CWC-NI3a.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-DsMBGqCc.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-DsMBGqCc.cjs");
|
|
2
|
+
const require_jsonld_cache = require("./jsonld-cache-DzxT9IXH.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 { _ as parseIri } from "./url-
|
|
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-CWC-NI3a.mjs";
|
|
2
|
+
import { a as normalizeJsonLdIris, c as jsonld_default, i as isTrustedIriOrigin, r as getJsonLdContext, t as compactJsonLdCache } from "./jsonld-cache-CKfgtxPB.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,8 +1,8 @@
|
|
|
1
1
|
const require_multibase = require("../multibase-B5Mea7Ip.cjs");
|
|
2
|
-
let node_test = require("node:test");
|
|
3
2
|
let node_assert = require("node:assert");
|
|
3
|
+
let _fedify_fixture = require("@fedify/fixture");
|
|
4
4
|
//#region src/multibase/multibase.test.ts
|
|
5
|
-
(0,
|
|
5
|
+
(0, _fedify_fixture.test)("multibase.encode and decode", async (t) => {
|
|
6
6
|
const testCases = [
|
|
7
7
|
[
|
|
8
8
|
"base16",
|
|
@@ -315,22 +315,22 @@ let node_assert = require("node:assert");
|
|
|
315
315
|
"Uw7fDr8O_8J-lsMO3w6_Dv_CfmI7wn6W28J-krw=="
|
|
316
316
|
]
|
|
317
317
|
];
|
|
318
|
-
for (const [name, input, expectedOutput] of testCases) await t.
|
|
318
|
+
for (const [name, input, expectedOutput] of testCases) await t.step(`Encoding/Decoding ${name} with ${input}`, () => {
|
|
319
319
|
(0, node_assert.deepStrictEqual)(require_multibase.decodeText(require_multibase.encodeMultibase(name, require_multibase.encodeText(input))), expectedOutput, `Encoding ${name} failed`);
|
|
320
320
|
(0, node_assert.deepStrictEqual)(require_multibase.decodeMultibase(expectedOutput), require_multibase.encodeText(input), `Decoding ${name} failed`);
|
|
321
321
|
(0, node_assert.deepStrictEqual)(require_multibase.decodeMultibase(require_multibase.encodeText(expectedOutput)), require_multibase.encodeText(input), `Decoding buffer of ${name} failed`);
|
|
322
322
|
});
|
|
323
|
-
await t.
|
|
323
|
+
await t.step("should allow base32pad full alphabet", () => {
|
|
324
324
|
const decoded = require_multibase.decodeMultibase("ctimaq4ygg2iegci7");
|
|
325
325
|
(0, node_assert.deepStrictEqual)(require_multibase.decodeMultibase(require_multibase.encodeMultibase("c", decoded)), decoded);
|
|
326
326
|
});
|
|
327
327
|
});
|
|
328
|
-
(0,
|
|
329
|
-
await t.
|
|
328
|
+
(0, _fedify_fixture.test)("constants", async (t) => {
|
|
329
|
+
await t.step("constants indexed by name", () => {
|
|
330
330
|
const names$1 = require_multibase.names;
|
|
331
331
|
(0, node_assert.deepStrictEqual)(Object.keys(names$1).length, 23);
|
|
332
332
|
});
|
|
333
|
-
await t.
|
|
333
|
+
await t.step("constants indexed by code", () => {
|
|
334
334
|
const codes$1 = require_multibase.codes;
|
|
335
335
|
(0, node_assert.deepStrictEqual)(Object.keys(codes$1).length, 23);
|
|
336
336
|
});
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { a as names, i as codes, n as encodeMultibase, o as decodeText, s as encodeText, t as decodeMultibase } from "../multibase-BPnF_L4e.mjs";
|
|
2
|
-
import { test } from "node:test";
|
|
3
2
|
import { deepStrictEqual } from "node:assert";
|
|
3
|
+
import { test } from "@fedify/fixture";
|
|
4
4
|
//#region src/multibase/multibase.test.ts
|
|
5
5
|
test("multibase.encode and decode", async (t) => {
|
|
6
6
|
const testCases = [
|
|
@@ -315,21 +315,21 @@ test("multibase.encode and decode", async (t) => {
|
|
|
315
315
|
"Uw7fDr8O_8J-lsMO3w6_Dv_CfmI7wn6W28J-krw=="
|
|
316
316
|
]
|
|
317
317
|
];
|
|
318
|
-
for (const [name, input, expectedOutput] of testCases) await t.
|
|
318
|
+
for (const [name, input, expectedOutput] of testCases) await t.step(`Encoding/Decoding ${name} with ${input}`, () => {
|
|
319
319
|
deepStrictEqual(decodeText(encodeMultibase(name, encodeText(input))), expectedOutput, `Encoding ${name} failed`);
|
|
320
320
|
deepStrictEqual(decodeMultibase(expectedOutput), encodeText(input), `Decoding ${name} failed`);
|
|
321
321
|
deepStrictEqual(decodeMultibase(encodeText(expectedOutput)), encodeText(input), `Decoding buffer of ${name} failed`);
|
|
322
322
|
});
|
|
323
|
-
await t.
|
|
323
|
+
await t.step("should allow base32pad full alphabet", () => {
|
|
324
324
|
const decoded = decodeMultibase("ctimaq4ygg2iegci7");
|
|
325
325
|
deepStrictEqual(decodeMultibase(encodeMultibase("c", decoded)), decoded);
|
|
326
326
|
});
|
|
327
327
|
});
|
|
328
328
|
test("constants", async (t) => {
|
|
329
|
-
await t.
|
|
329
|
+
await t.step("constants indexed by name", () => {
|
|
330
330
|
deepStrictEqual(Object.keys(names).length, 23);
|
|
331
331
|
});
|
|
332
|
-
await t.
|
|
332
|
+
await t.step("constants indexed by code", () => {
|
|
333
333
|
deepStrictEqual(Object.keys(codes).length, 23);
|
|
334
334
|
});
|
|
335
335
|
});
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
import { t as preloadedContexts } from "./contexts-CIKsin4e.mjs";
|
|
2
|
+
import { h as parseGatewayOrigin, o as formatIri, t as GATEWAY_HINT_PARAMETER } from "./url-CWC-NI3a.mjs";
|
|
3
|
+
import { o as registerDocumentLoaderWrapper, s as unwrapReleasedDocumentLoader } from "./jsonld-cache-CKfgtxPB.mjs";
|
|
4
|
+
import { getLogger } from "@logtape/logtape";
|
|
5
|
+
import "@opentelemetry/api";
|
|
6
|
+
//#region src/internal/portable-dereference.ts
|
|
7
|
+
const logger = getLogger([
|
|
8
|
+
"fedify",
|
|
9
|
+
"vocab",
|
|
10
|
+
"gateway"
|
|
11
|
+
]);
|
|
12
|
+
/**
|
|
13
|
+
* The maximum number of `@gateway` location hints to try for one reference.
|
|
14
|
+
* Hints come from possibly untrusted documents, so they are bounded to keep
|
|
15
|
+
* a single accessor call from fanning out to many servers.
|
|
16
|
+
*/
|
|
17
|
+
const MAX_GATEWAY_HINTS = 5;
|
|
18
|
+
const BASELINE_CONTEXT_URLS = /* @__PURE__ */ new Set([
|
|
19
|
+
"https://w3id.org/identity/v1",
|
|
20
|
+
"https://www.w3.org/ns/activitystreams",
|
|
21
|
+
"https://w3id.org/security/v1",
|
|
22
|
+
"https://w3id.org/security/data-integrity/v1"
|
|
23
|
+
]);
|
|
24
|
+
/**
|
|
25
|
+
* Checks whether a URL is an FEP-ef61 portable ActivityPub IRI.
|
|
26
|
+
*
|
|
27
|
+
* @internal Technically exported for generated vocabulary classes, but not
|
|
28
|
+
* part of the public API contract. This is not considered public API for
|
|
29
|
+
* Semantic Versioning decisions.
|
|
30
|
+
*/
|
|
31
|
+
function isPortableIri(url) {
|
|
32
|
+
return url.protocol === "ap+ef61:" || url.protocol === "ap:";
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Picks the ordered list of FEP-ef61 gateways to fetch a portable IRI from.
|
|
36
|
+
*
|
|
37
|
+
* If `gateways` is given, it is used as is (even when empty), and `@gateway`
|
|
38
|
+
* location hints in the IRI are ignored. Otherwise, up to
|
|
39
|
+
* {@link MAX_GATEWAY_HINTS} valid `@gateway` hints are used. Duplicate
|
|
40
|
+
* gateways are dropped in both cases.
|
|
41
|
+
*
|
|
42
|
+
* @throws {TypeError} If an explicit gateway is not an HTTP(S) origin.
|
|
43
|
+
* @internal
|
|
44
|
+
*/
|
|
45
|
+
function getPortableGatewayCandidates(url, gateways) {
|
|
46
|
+
const candidates = [];
|
|
47
|
+
const seen = /* @__PURE__ */ new Set();
|
|
48
|
+
const add = (gateway) => {
|
|
49
|
+
if (seen.has(gateway.href)) return;
|
|
50
|
+
seen.add(gateway.href);
|
|
51
|
+
candidates.push(gateway);
|
|
52
|
+
};
|
|
53
|
+
if (gateways != null) {
|
|
54
|
+
for (const gateway of gateways) {
|
|
55
|
+
const parsed = parseGatewayOrigin(gateway);
|
|
56
|
+
if (parsed == null) throw new TypeError("FEP-ef61 gateways must be HTTP(S) origins with no credentials, path, query, or fragment: " + String(gateway));
|
|
57
|
+
add(parsed);
|
|
58
|
+
}
|
|
59
|
+
return candidates;
|
|
60
|
+
}
|
|
61
|
+
for (const hint of new URLSearchParams(url.search).getAll(GATEWAY_HINT_PARAMETER)) {
|
|
62
|
+
if (candidates.length >= MAX_GATEWAY_HINTS) break;
|
|
63
|
+
const parsed = parseGatewayOrigin(hint);
|
|
64
|
+
if (parsed == null) {
|
|
65
|
+
logger.debug("Ignoring an invalid FEP-ef61 gateway hint {hint} in {url}.", {
|
|
66
|
+
hint,
|
|
67
|
+
url: formatIri(url)
|
|
68
|
+
});
|
|
69
|
+
continue;
|
|
70
|
+
}
|
|
71
|
+
add(parsed);
|
|
72
|
+
}
|
|
73
|
+
return candidates;
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Creates a context loader that returns the same context documents for the
|
|
77
|
+
* whole dereference operation, so that the identity check, the proof
|
|
78
|
+
* verifier, and the parser interpret the fetched document identically even
|
|
79
|
+
* if the underlying loader is nondeterministic. Failed loads are not
|
|
80
|
+
* remembered, so a transient failure does not affect the next gateway.
|
|
81
|
+
*
|
|
82
|
+
* The parsed object keeps the loader for its own later dereferences, so
|
|
83
|
+
* `release()` turns it into a plain pass-through to the underlying loader
|
|
84
|
+
* once the operation is over.
|
|
85
|
+
*
|
|
86
|
+
* @internal Technically exported for generated vocabulary classes, but not
|
|
87
|
+
* part of the public API contract. This is not considered public API for
|
|
88
|
+
* Semantic Versioning decisions.
|
|
89
|
+
*/
|
|
90
|
+
function createSnapshotContextLoader(contextLoader, suppressError) {
|
|
91
|
+
contextLoader = unwrapReleasedDocumentLoader(contextLoader);
|
|
92
|
+
const cache = /* @__PURE__ */ new Map();
|
|
93
|
+
let released = false;
|
|
94
|
+
const release = () => {
|
|
95
|
+
released = true;
|
|
96
|
+
state.released = true;
|
|
97
|
+
cache.clear();
|
|
98
|
+
};
|
|
99
|
+
const loader = async (url, options) => {
|
|
100
|
+
if (released) return await contextLoader(url, options);
|
|
101
|
+
const key = URL.canParse(url) ? new URL(url).href : url;
|
|
102
|
+
if (BASELINE_CONTEXT_URLS.has(key)) return {
|
|
103
|
+
contextUrl: null,
|
|
104
|
+
document: structuredClone(preloadedContexts[key]),
|
|
105
|
+
documentUrl: key
|
|
106
|
+
};
|
|
107
|
+
let promise = cache.get(key);
|
|
108
|
+
if (promise == null) {
|
|
109
|
+
const loading = contextLoader(url, suppressError ? {
|
|
110
|
+
...options,
|
|
111
|
+
suppressError: true
|
|
112
|
+
} : options).then((document) => structuredClone(document));
|
|
113
|
+
promise = loading;
|
|
114
|
+
cache.set(key, loading);
|
|
115
|
+
loading.catch(() => {
|
|
116
|
+
if (cache.get(key) === loading) cache.delete(key);
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
return structuredClone(await promise);
|
|
120
|
+
};
|
|
121
|
+
const state = {
|
|
122
|
+
base: contextLoader,
|
|
123
|
+
released: false
|
|
124
|
+
};
|
|
125
|
+
registerDocumentLoaderWrapper(loader, state);
|
|
126
|
+
return {
|
|
127
|
+
loader,
|
|
128
|
+
release
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
//#endregion
|
|
132
|
+
export { getPortableGatewayCandidates as n, isPortableIri as r, createSnapshotContextLoader as t };
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
const require_contexts = require("./contexts-DizzBjz4.cjs");
|
|
2
|
+
const require_url = require("./url-DsMBGqCc.cjs");
|
|
3
|
+
const require_jsonld_cache = require("./jsonld-cache-DzxT9IXH.cjs");
|
|
4
|
+
let _logtape_logtape = require("@logtape/logtape");
|
|
5
|
+
require("@opentelemetry/api");
|
|
6
|
+
//#region src/internal/portable-dereference.ts
|
|
7
|
+
const logger = (0, _logtape_logtape.getLogger)([
|
|
8
|
+
"fedify",
|
|
9
|
+
"vocab",
|
|
10
|
+
"gateway"
|
|
11
|
+
]);
|
|
12
|
+
/**
|
|
13
|
+
* The maximum number of `@gateway` location hints to try for one reference.
|
|
14
|
+
* Hints come from possibly untrusted documents, so they are bounded to keep
|
|
15
|
+
* a single accessor call from fanning out to many servers.
|
|
16
|
+
*/
|
|
17
|
+
const MAX_GATEWAY_HINTS = 5;
|
|
18
|
+
const BASELINE_CONTEXT_URLS = /* @__PURE__ */ new Set([
|
|
19
|
+
"https://w3id.org/identity/v1",
|
|
20
|
+
"https://www.w3.org/ns/activitystreams",
|
|
21
|
+
"https://w3id.org/security/v1",
|
|
22
|
+
"https://w3id.org/security/data-integrity/v1"
|
|
23
|
+
]);
|
|
24
|
+
/**
|
|
25
|
+
* Checks whether a URL is an FEP-ef61 portable ActivityPub IRI.
|
|
26
|
+
*
|
|
27
|
+
* @internal Technically exported for generated vocabulary classes, but not
|
|
28
|
+
* part of the public API contract. This is not considered public API for
|
|
29
|
+
* Semantic Versioning decisions.
|
|
30
|
+
*/
|
|
31
|
+
function isPortableIri(url) {
|
|
32
|
+
return url.protocol === "ap+ef61:" || url.protocol === "ap:";
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Picks the ordered list of FEP-ef61 gateways to fetch a portable IRI from.
|
|
36
|
+
*
|
|
37
|
+
* If `gateways` is given, it is used as is (even when empty), and `@gateway`
|
|
38
|
+
* location hints in the IRI are ignored. Otherwise, up to
|
|
39
|
+
* {@link MAX_GATEWAY_HINTS} valid `@gateway` hints are used. Duplicate
|
|
40
|
+
* gateways are dropped in both cases.
|
|
41
|
+
*
|
|
42
|
+
* @throws {TypeError} If an explicit gateway is not an HTTP(S) origin.
|
|
43
|
+
* @internal
|
|
44
|
+
*/
|
|
45
|
+
function getPortableGatewayCandidates(url, gateways) {
|
|
46
|
+
const candidates = [];
|
|
47
|
+
const seen = /* @__PURE__ */ new Set();
|
|
48
|
+
const add = (gateway) => {
|
|
49
|
+
if (seen.has(gateway.href)) return;
|
|
50
|
+
seen.add(gateway.href);
|
|
51
|
+
candidates.push(gateway);
|
|
52
|
+
};
|
|
53
|
+
if (gateways != null) {
|
|
54
|
+
for (const gateway of gateways) {
|
|
55
|
+
const parsed = require_url.parseGatewayOrigin(gateway);
|
|
56
|
+
if (parsed == null) throw new TypeError("FEP-ef61 gateways must be HTTP(S) origins with no credentials, path, query, or fragment: " + String(gateway));
|
|
57
|
+
add(parsed);
|
|
58
|
+
}
|
|
59
|
+
return candidates;
|
|
60
|
+
}
|
|
61
|
+
for (const hint of new URLSearchParams(url.search).getAll(require_url.GATEWAY_HINT_PARAMETER)) {
|
|
62
|
+
if (candidates.length >= MAX_GATEWAY_HINTS) break;
|
|
63
|
+
const parsed = require_url.parseGatewayOrigin(hint);
|
|
64
|
+
if (parsed == null) {
|
|
65
|
+
logger.debug("Ignoring an invalid FEP-ef61 gateway hint {hint} in {url}.", {
|
|
66
|
+
hint,
|
|
67
|
+
url: require_url.formatIri(url)
|
|
68
|
+
});
|
|
69
|
+
continue;
|
|
70
|
+
}
|
|
71
|
+
add(parsed);
|
|
72
|
+
}
|
|
73
|
+
return candidates;
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Creates a context loader that returns the same context documents for the
|
|
77
|
+
* whole dereference operation, so that the identity check, the proof
|
|
78
|
+
* verifier, and the parser interpret the fetched document identically even
|
|
79
|
+
* if the underlying loader is nondeterministic. Failed loads are not
|
|
80
|
+
* remembered, so a transient failure does not affect the next gateway.
|
|
81
|
+
*
|
|
82
|
+
* The parsed object keeps the loader for its own later dereferences, so
|
|
83
|
+
* `release()` turns it into a plain pass-through to the underlying loader
|
|
84
|
+
* once the operation is over.
|
|
85
|
+
*
|
|
86
|
+
* @internal Technically exported for generated vocabulary classes, but not
|
|
87
|
+
* part of the public API contract. This is not considered public API for
|
|
88
|
+
* Semantic Versioning decisions.
|
|
89
|
+
*/
|
|
90
|
+
function createSnapshotContextLoader(contextLoader, suppressError) {
|
|
91
|
+
contextLoader = require_jsonld_cache.unwrapReleasedDocumentLoader(contextLoader);
|
|
92
|
+
const cache = /* @__PURE__ */ new Map();
|
|
93
|
+
let released = false;
|
|
94
|
+
const release = () => {
|
|
95
|
+
released = true;
|
|
96
|
+
state.released = true;
|
|
97
|
+
cache.clear();
|
|
98
|
+
};
|
|
99
|
+
const loader = async (url, options) => {
|
|
100
|
+
if (released) return await contextLoader(url, options);
|
|
101
|
+
const key = URL.canParse(url) ? new URL(url).href : url;
|
|
102
|
+
if (BASELINE_CONTEXT_URLS.has(key)) return {
|
|
103
|
+
contextUrl: null,
|
|
104
|
+
document: structuredClone(require_contexts.preloadedContexts[key]),
|
|
105
|
+
documentUrl: key
|
|
106
|
+
};
|
|
107
|
+
let promise = cache.get(key);
|
|
108
|
+
if (promise == null) {
|
|
109
|
+
const loading = contextLoader(url, suppressError ? {
|
|
110
|
+
...options,
|
|
111
|
+
suppressError: true
|
|
112
|
+
} : options).then((document) => structuredClone(document));
|
|
113
|
+
promise = loading;
|
|
114
|
+
cache.set(key, loading);
|
|
115
|
+
loading.catch(() => {
|
|
116
|
+
if (cache.get(key) === loading) cache.delete(key);
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
return structuredClone(await promise);
|
|
120
|
+
};
|
|
121
|
+
const state = {
|
|
122
|
+
base: contextLoader,
|
|
123
|
+
released: false
|
|
124
|
+
};
|
|
125
|
+
require_jsonld_cache.registerDocumentLoaderWrapper(loader, state);
|
|
126
|
+
return {
|
|
127
|
+
loader,
|
|
128
|
+
release
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
//#endregion
|
|
132
|
+
Object.defineProperty(exports, "createSnapshotContextLoader", {
|
|
133
|
+
enumerable: true,
|
|
134
|
+
get: function() {
|
|
135
|
+
return createSnapshotContextLoader;
|
|
136
|
+
}
|
|
137
|
+
});
|
|
138
|
+
Object.defineProperty(exports, "getPortableGatewayCandidates", {
|
|
139
|
+
enumerable: true,
|
|
140
|
+
get: function() {
|
|
141
|
+
return getPortableGatewayCandidates;
|
|
142
|
+
}
|
|
143
|
+
});
|
|
144
|
+
Object.defineProperty(exports, "isPortableIri", {
|
|
145
|
+
enumerable: true,
|
|
146
|
+
get: function() {
|
|
147
|
+
return isPortableIri;
|
|
148
|
+
}
|
|
149
|
+
});
|
|
@@ -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-CBCk-e0R.cjs");
|
|
2
|
+
const require_body = require("./body-C7LfsQJ3.cjs");
|
|
3
|
+
const require_url = require("./url-DsMBGqCc.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,6 +1,6 @@
|
|
|
1
|
-
import { r as getUserAgent } from "./request-
|
|
2
|
-
import { r as readBoundedBytes } from "./body-
|
|
3
|
-
import { g as parseGatewayUrl, x as validatePublicUrl } from "./url-
|
|
1
|
+
import { r as getUserAgent } from "./request-wKyc91L5.mjs";
|
|
2
|
+
import { r as readBoundedBytes } from "./body-D50XSh7s.mjs";
|
|
3
|
+
import { g as parseGatewayUrl, x as validatePublicUrl } from "./url-CWC-NI3a.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,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-Bj5FXpUr.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-D4NJcqkI.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";
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
+
const require_url = require("./url-DsMBGqCc.cjs");
|
|
3
|
+
const require_key = require("./key-C-AYkdJJ.cjs");
|
|
4
|
+
const require_digest = require("./digest-3FeH2Y-Q.cjs");
|
|
5
|
+
const require_portable_dereference = require("./portable-dereference-CyFYTBnr.cjs");
|
|
6
|
+
let node_assert_strict = require("node:assert/strict");
|
|
7
|
+
let _fedify_fixture = require("@fedify/fixture");
|
|
8
|
+
//#region src/portable-workers.test.ts
|
|
9
|
+
const portableId = "ap://did:key:z6Mkabc/actor";
|
|
10
|
+
(0, _fedify_fixture.test)("vocab-runtime portable URI formats on Workers", () => {
|
|
11
|
+
(0, node_assert_strict.equal)(require_url.formatIri(require_url.parseIri(portableId)), "ap+ef61://did:key:z6Mkabc/actor");
|
|
12
|
+
(0, node_assert_strict.equal)(require_url.canonicalizePortableUri(`${portableId}?@gateway=https%3A%2F%2Fa.example`), "ap+ef61://did:key:z6Mkabc/actor");
|
|
13
|
+
});
|
|
14
|
+
(0, _fedify_fixture.test)("vocab-runtime compatible IDs on Workers", () => {
|
|
15
|
+
const compatible = require_url.toCompatibleEf61Id(portableId, "https://gw.example");
|
|
16
|
+
(0, node_assert_strict.equal)(compatible.href, "https://gw.example/.well-known/apgateway/did:key:z6Mkabc/actor");
|
|
17
|
+
(0, node_assert_strict.equal)(require_url.formatIri(require_url.fromCompatibleEf61Id(compatible)), "ap+ef61://did:key:z6Mkabc/actor");
|
|
18
|
+
});
|
|
19
|
+
(0, _fedify_fixture.test)("vocab-runtime did:key on Workers", async () => {
|
|
20
|
+
const keys = await crypto.subtle.generateKey("Ed25519", true, ["sign", "verify"]);
|
|
21
|
+
const imported = await require_key.importDidKey(await require_key.exportDidKey(keys.publicKey));
|
|
22
|
+
(0, node_assert_strict.deepStrictEqual)(new Uint8Array(await crypto.subtle.exportKey("raw", imported)), new Uint8Array(await crypto.subtle.exportKey("raw", keys.publicKey)));
|
|
23
|
+
});
|
|
24
|
+
(0, _fedify_fixture.test)("vocab-runtime hashlink on Workers", async () => {
|
|
25
|
+
const bytes = new TextEncoder().encode("portable media");
|
|
26
|
+
const digest = await require_digest.computeDigestMultibase(bytes);
|
|
27
|
+
(0, node_assert_strict.equal)(await require_digest.verifyHashlink(bytes, require_digest.createHashlink(digest)), true);
|
|
28
|
+
(0, node_assert_strict.equal)(await require_digest.verifyHashlink(new TextEncoder().encode("tampered"), require_digest.createHashlink(digest)), false);
|
|
29
|
+
});
|
|
30
|
+
(0, _fedify_fixture.test)("vocab-runtime gateway candidates on Workers", () => {
|
|
31
|
+
const hinted = require_url.parseIri(`${portableId}?@gateway=https%3A%2F%2Fhint.example`);
|
|
32
|
+
(0, node_assert_strict.deepStrictEqual)(require_portable_dereference.getPortableGatewayCandidates(hinted, ["https://first.example"]).map((url) => url.href), ["https://first.example/"]);
|
|
33
|
+
(0, node_assert_strict.deepStrictEqual)(require_portable_dereference.getPortableGatewayCandidates(hinted).map((url) => url.href), ["https://hint.example/"]);
|
|
34
|
+
});
|
|
35
|
+
//#endregion
|
|
36
|
+
exports.testDefinitions = _fedify_fixture.testDefinitions;
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { _ as parseIri, i as canonicalizePortableUri, o as formatIri, s as fromCompatibleEf61Id, y as toCompatibleEf61Id } from "./url-CWC-NI3a.mjs";
|
|
2
|
+
import { i as importDidKey, t as exportDidKey } from "./key-C2Db_TAJ.mjs";
|
|
3
|
+
import { n as createHashlink, o as verifyHashlink, t as computeDigestMultibase } from "./digest-COC7xDiQ.mjs";
|
|
4
|
+
import { n as getPortableGatewayCandidates } from "./portable-dereference-C_cwGjz6.mjs";
|
|
5
|
+
import { deepStrictEqual, equal } from "node:assert/strict";
|
|
6
|
+
import { test, testDefinitions } from "@fedify/fixture";
|
|
7
|
+
//#region src/portable-workers.test.ts
|
|
8
|
+
const portableId = "ap://did:key:z6Mkabc/actor";
|
|
9
|
+
test("vocab-runtime portable URI formats on Workers", () => {
|
|
10
|
+
equal(formatIri(parseIri(portableId)), "ap+ef61://did:key:z6Mkabc/actor");
|
|
11
|
+
equal(canonicalizePortableUri(`${portableId}?@gateway=https%3A%2F%2Fa.example`), "ap+ef61://did:key:z6Mkabc/actor");
|
|
12
|
+
});
|
|
13
|
+
test("vocab-runtime compatible IDs on Workers", () => {
|
|
14
|
+
const compatible = toCompatibleEf61Id(portableId, "https://gw.example");
|
|
15
|
+
equal(compatible.href, "https://gw.example/.well-known/apgateway/did:key:z6Mkabc/actor");
|
|
16
|
+
equal(formatIri(fromCompatibleEf61Id(compatible)), "ap+ef61://did:key:z6Mkabc/actor");
|
|
17
|
+
});
|
|
18
|
+
test("vocab-runtime did:key on Workers", async () => {
|
|
19
|
+
const keys = await crypto.subtle.generateKey("Ed25519", true, ["sign", "verify"]);
|
|
20
|
+
const imported = await importDidKey(await exportDidKey(keys.publicKey));
|
|
21
|
+
deepStrictEqual(new Uint8Array(await crypto.subtle.exportKey("raw", imported)), new Uint8Array(await crypto.subtle.exportKey("raw", keys.publicKey)));
|
|
22
|
+
});
|
|
23
|
+
test("vocab-runtime hashlink on Workers", async () => {
|
|
24
|
+
const bytes = new TextEncoder().encode("portable media");
|
|
25
|
+
const digest = await computeDigestMultibase(bytes);
|
|
26
|
+
equal(await verifyHashlink(bytes, createHashlink(digest)), true);
|
|
27
|
+
equal(await verifyHashlink(new TextEncoder().encode("tampered"), createHashlink(digest)), false);
|
|
28
|
+
});
|
|
29
|
+
test("vocab-runtime gateway candidates on Workers", () => {
|
|
30
|
+
const hinted = parseIri(`${portableId}?@gateway=https%3A%2F%2Fhint.example`);
|
|
31
|
+
deepStrictEqual(getPortableGatewayCandidates(hinted, ["https://first.example"]).map((url) => url.href), ["https://first.example/"]);
|
|
32
|
+
deepStrictEqual(getPortableGatewayCandidates(hinted).map((url) => url.href), ["https://hint.example/"]);
|
|
33
|
+
});
|
|
34
|
+
//#endregion
|
|
35
|
+
export { testDefinitions };
|
|
@@ -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.2245+042b9587";
|
|
7
7
|
//#endregion
|
|
8
8
|
//#region src/request.ts
|
|
9
9
|
/**
|