@fedify/vocab 2.4.0-dev.2122 → 2.4.0-dev.2129
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 +1 -1
- package/dist/mod.cjs +3065 -737
- package/dist/mod.d.cts +708 -2
- package/dist/mod.d.ts +708 -2
- package/dist/mod.js +3066 -738
- package/dist-tests/{actor-CPhFJvyu.mjs → actor-Jl479WDt.mjs} +2 -2
- package/dist-tests/actor.test.mjs +3 -3
- package/dist-tests/{esm-C9nxP5_e.mjs → esm-CU6UbTlZ.mjs} +1 -1
- package/dist-tests/gateway.test.mjs +249 -3
- package/dist-tests/{lookup-Do6hKwnJ.mjs → lookup-YM9st5n8.mjs} +4 -2
- package/dist-tests/lookup.test.mjs +3 -3
- package/dist-tests/portable-lookup.test.mjs +4 -4
- package/dist-tests/signed-representation.test.mjs +1 -1
- package/dist-tests/translation.test.mjs +3 -3
- package/dist-tests/type.test.mjs +1 -1
- package/dist-tests/{vocab-BHtBRusa.mjs → vocab-DFtGFGsb.mjs} +3063 -737
- package/dist-tests/vocab.test.mjs +360 -2
- package/package.json +4 -4
- package/src/gateway.test.ts +335 -1
- package/src/lookup.ts +7 -2
- package/src/preprocessors.ts +1 -1
- package/src/vocab.test.ts +494 -0
package/src/gateway.test.ts
CHANGED
|
@@ -15,12 +15,13 @@ import {
|
|
|
15
15
|
} from "@fedify/vocab-runtime";
|
|
16
16
|
import fetchMock from "fetch-mock";
|
|
17
17
|
import { deepStrictEqual, ok, rejects } from "node:assert/strict";
|
|
18
|
-
import { traverseCollection } from "./lookup.ts";
|
|
18
|
+
import { lookupObject, traverseCollection } from "./lookup.ts";
|
|
19
19
|
import { assertInstanceOf } from "./utils.ts";
|
|
20
20
|
import {
|
|
21
21
|
Collection,
|
|
22
22
|
CollectionPage,
|
|
23
23
|
Create,
|
|
24
|
+
Image,
|
|
24
25
|
Note,
|
|
25
26
|
OrderedCollection,
|
|
26
27
|
Person,
|
|
@@ -1858,3 +1859,336 @@ test("singular accessors drop anonymous objects embedded in unsecured collection
|
|
|
1858
1859
|
[],
|
|
1859
1860
|
);
|
|
1860
1861
|
});
|
|
1862
|
+
|
|
1863
|
+
function createCompatibleActivity(
|
|
1864
|
+
object: unknown,
|
|
1865
|
+
id: string = gatewayUrl("https://gw.example", "/activities/1"),
|
|
1866
|
+
options: { verifyPortableObject?: PortableObjectVerifier } = {},
|
|
1867
|
+
): Promise<Create> {
|
|
1868
|
+
return Create.fromJsonLd({
|
|
1869
|
+
"@context": "https://www.w3.org/ns/activitystreams",
|
|
1870
|
+
type: "Create",
|
|
1871
|
+
id,
|
|
1872
|
+
object,
|
|
1873
|
+
}, { contextLoader: mockDocumentLoader, ...options });
|
|
1874
|
+
}
|
|
1875
|
+
|
|
1876
|
+
test("accessors of objects with compatible identifiers dereference references as portable objects", async (t) => {
|
|
1877
|
+
await t.step("verifying the object at the reference", async () => {
|
|
1878
|
+
const documentLoader = createLoader({
|
|
1879
|
+
[compatibleNoteId]: { ...note(), id: compatibleNoteId },
|
|
1880
|
+
});
|
|
1881
|
+
const verifier = createRecordingVerifier();
|
|
1882
|
+
const activity = await createCompatibleActivity(compatibleNoteId);
|
|
1883
|
+
const object = await activity.getObject({
|
|
1884
|
+
documentLoader,
|
|
1885
|
+
contextLoader: mockDocumentLoader,
|
|
1886
|
+
verifyPortableObject: verifier,
|
|
1887
|
+
});
|
|
1888
|
+
assertInstanceOf(object, Note);
|
|
1889
|
+
deepStrictEqual(object.id, new URL(compatibleNoteId));
|
|
1890
|
+
deepStrictEqual(documentLoader.fetched, [compatibleNoteId]);
|
|
1891
|
+
deepStrictEqual(verifier.calls.length, 1);
|
|
1892
|
+
deepStrictEqual(verifier.calls[0].options.gatewayHints, [
|
|
1893
|
+
new URL("https://gw.example"),
|
|
1894
|
+
]);
|
|
1895
|
+
ok(verifier.calls[0].options.referrer?.object === activity);
|
|
1896
|
+
// The root itself is not marked as verified:
|
|
1897
|
+
deepStrictEqual(verifier.calls[0].options.referrer?.acceptance, undefined);
|
|
1898
|
+
});
|
|
1899
|
+
|
|
1900
|
+
await t.step("rejecting an unsigned object at the reference", async () => {
|
|
1901
|
+
const documentLoader = createLoader({
|
|
1902
|
+
[compatibleNoteId]: { ...note(), id: compatibleNoteId },
|
|
1903
|
+
});
|
|
1904
|
+
const activity = await createCompatibleActivity(compatibleNoteId);
|
|
1905
|
+
deepStrictEqual(
|
|
1906
|
+
await activity.getObject({
|
|
1907
|
+
documentLoader,
|
|
1908
|
+
contextLoader: mockDocumentLoader,
|
|
1909
|
+
verifyPortableObject: createVerifier(false),
|
|
1910
|
+
}),
|
|
1911
|
+
null,
|
|
1912
|
+
);
|
|
1913
|
+
// Even with crossOrigin: "trust":
|
|
1914
|
+
deepStrictEqual(
|
|
1915
|
+
await Array.fromAsync(
|
|
1916
|
+
(await createCompatibleActivity([compatibleNoteId])).getObjects({
|
|
1917
|
+
documentLoader,
|
|
1918
|
+
contextLoader: mockDocumentLoader,
|
|
1919
|
+
verifyPortableObject: createVerifier(false),
|
|
1920
|
+
crossOrigin: "trust",
|
|
1921
|
+
}),
|
|
1922
|
+
),
|
|
1923
|
+
[],
|
|
1924
|
+
);
|
|
1925
|
+
});
|
|
1926
|
+
|
|
1927
|
+
await t.step("without verifyPortableObject", async () => {
|
|
1928
|
+
const documentLoader = createLoader({
|
|
1929
|
+
[compatibleNoteId]: { ...note(), id: compatibleNoteId },
|
|
1930
|
+
});
|
|
1931
|
+
const activity = await createCompatibleActivity(compatibleNoteId);
|
|
1932
|
+
await rejects(
|
|
1933
|
+
async () =>
|
|
1934
|
+
await activity.getObject({
|
|
1935
|
+
documentLoader,
|
|
1936
|
+
contextLoader: mockDocumentLoader,
|
|
1937
|
+
}),
|
|
1938
|
+
TypeError,
|
|
1939
|
+
);
|
|
1940
|
+
deepStrictEqual(
|
|
1941
|
+
await activity.getObject({
|
|
1942
|
+
documentLoader,
|
|
1943
|
+
contextLoader: mockDocumentLoader,
|
|
1944
|
+
suppressError: true,
|
|
1945
|
+
}),
|
|
1946
|
+
null,
|
|
1947
|
+
);
|
|
1948
|
+
deepStrictEqual(documentLoader.fetched, []);
|
|
1949
|
+
});
|
|
1950
|
+
|
|
1951
|
+
await t.step("embedded objects with compatible identifiers", async () => {
|
|
1952
|
+
// Even on the same gateway and with the same DID, an embedded object
|
|
1953
|
+
// with a compatible identifier is verified on its own:
|
|
1954
|
+
const documentLoader = createLoader({ [compatibleNoteId]: note() });
|
|
1955
|
+
const verifier = createRecordingVerifier();
|
|
1956
|
+
const activity = await createCompatibleActivity({
|
|
1957
|
+
type: "Note",
|
|
1958
|
+
id: compatibleNoteId,
|
|
1959
|
+
content: "Embedded",
|
|
1960
|
+
});
|
|
1961
|
+
const object = await activity.getObject({
|
|
1962
|
+
documentLoader,
|
|
1963
|
+
contextLoader: mockDocumentLoader,
|
|
1964
|
+
verifyPortableObject: verifier,
|
|
1965
|
+
});
|
|
1966
|
+
assertInstanceOf(object, Note);
|
|
1967
|
+
deepStrictEqual(object.content, "Portable note");
|
|
1968
|
+
deepStrictEqual(verifier.calls.length, 1);
|
|
1969
|
+
});
|
|
1970
|
+
|
|
1971
|
+
await t.step("objects with malformed compatible identifiers", async () => {
|
|
1972
|
+
const malformedId = `${
|
|
1973
|
+
gatewayUrl("https://gw.example", "/activities/1")
|
|
1974
|
+
}?@gateway=https%3A%2F%2Fgw2.example`;
|
|
1975
|
+
// Portable references need a verifier:
|
|
1976
|
+
const activity = await createCompatibleActivity(objectId, malformedId);
|
|
1977
|
+
await rejects(
|
|
1978
|
+
async () =>
|
|
1979
|
+
await activity.getObject({
|
|
1980
|
+
documentLoader: createLoader({}),
|
|
1981
|
+
contextLoader: mockDocumentLoader,
|
|
1982
|
+
}),
|
|
1983
|
+
TypeError,
|
|
1984
|
+
);
|
|
1985
|
+
// Ordinary HTTP(S) references are still fetched without one:
|
|
1986
|
+
const plainUrl = "https://example.com/notes/1";
|
|
1987
|
+
const plainLoader = createLoader({
|
|
1988
|
+
[plainUrl]: { ...note(), id: plainUrl },
|
|
1989
|
+
});
|
|
1990
|
+
const plain = await (await createCompatibleActivity(plainUrl, malformedId))
|
|
1991
|
+
.getObject({
|
|
1992
|
+
documentLoader: plainLoader,
|
|
1993
|
+
contextLoader: mockDocumentLoader,
|
|
1994
|
+
});
|
|
1995
|
+
deepStrictEqual(plain?.id, new URL(plainUrl));
|
|
1996
|
+
// ...unless the response turns out to be a portable object:
|
|
1997
|
+
const claimLoader = createLoader({ [plainUrl]: note() });
|
|
1998
|
+
await rejects(
|
|
1999
|
+
async () =>
|
|
2000
|
+
await (await createCompatibleActivity(plainUrl, malformedId))
|
|
2001
|
+
.getObject({
|
|
2002
|
+
documentLoader: claimLoader,
|
|
2003
|
+
contextLoader: mockDocumentLoader,
|
|
2004
|
+
}),
|
|
2005
|
+
TypeError,
|
|
2006
|
+
);
|
|
2007
|
+
});
|
|
2008
|
+
|
|
2009
|
+
await t.step("objects with ordinary HTTP(S) identifiers", async () => {
|
|
2010
|
+
const documentLoader = createLoader({
|
|
2011
|
+
[compatibleNoteId]: { ...note(), id: compatibleNoteId },
|
|
2012
|
+
});
|
|
2013
|
+
const object = await (await createHttpActivity(compatibleNoteId))
|
|
2014
|
+
.getObject({ documentLoader, contextLoader: mockDocumentLoader });
|
|
2015
|
+
deepStrictEqual(object?.id, new URL(compatibleNoteId));
|
|
2016
|
+
});
|
|
2017
|
+
});
|
|
2018
|
+
|
|
2019
|
+
test("objects use the verifyPortableObject option they were parsed with by default", async (t) => {
|
|
2020
|
+
const attributedNote = (id: string) => ({
|
|
2021
|
+
...note(id),
|
|
2022
|
+
attributedTo: `ap://${did}/actor`,
|
|
2023
|
+
});
|
|
2024
|
+
const actor = {
|
|
2025
|
+
"@context": "https://www.w3.org/ns/activitystreams",
|
|
2026
|
+
id: `ap://${did}/actor`,
|
|
2027
|
+
type: "Person",
|
|
2028
|
+
inbox: `ap://${did}/actor/inbox`,
|
|
2029
|
+
};
|
|
2030
|
+
const documentLoader = createLoader({
|
|
2031
|
+
[compatibleNoteId]: attributedNote(compatibleNoteId),
|
|
2032
|
+
[gatewayUrl("https://gw.example", "/actor")]: actor,
|
|
2033
|
+
});
|
|
2034
|
+
|
|
2035
|
+
await t.step("fromJsonLd()", async () => {
|
|
2036
|
+
const verifier = createRecordingVerifier();
|
|
2037
|
+
const activity = await createCompatibleActivity(
|
|
2038
|
+
compatibleNoteId,
|
|
2039
|
+
undefined,
|
|
2040
|
+
{ verifyPortableObject: verifier },
|
|
2041
|
+
);
|
|
2042
|
+
const object = await activity.getObject({
|
|
2043
|
+
documentLoader,
|
|
2044
|
+
contextLoader: mockDocumentLoader,
|
|
2045
|
+
});
|
|
2046
|
+
assertInstanceOf(object, Note);
|
|
2047
|
+
deepStrictEqual(verifier.calls.length, 1);
|
|
2048
|
+
// Fetched objects inherit the default:
|
|
2049
|
+
const author = await object.getAttribution({
|
|
2050
|
+
documentLoader,
|
|
2051
|
+
contextLoader: mockDocumentLoader,
|
|
2052
|
+
gateways: ["https://gw.example"],
|
|
2053
|
+
});
|
|
2054
|
+
assertInstanceOf(author, Person);
|
|
2055
|
+
deepStrictEqual(verifier.calls.length, 2);
|
|
2056
|
+
// Embedded objects too:
|
|
2057
|
+
const embedding = await createCompatibleActivity(
|
|
2058
|
+
attributedNote(`ap://${did}/objects/2`),
|
|
2059
|
+
`ap://${did}/activities/1`,
|
|
2060
|
+
{ verifyPortableObject: verifier },
|
|
2061
|
+
);
|
|
2062
|
+
const embedded = await embedding.getObject({
|
|
2063
|
+
documentLoader,
|
|
2064
|
+
contextLoader: mockDocumentLoader,
|
|
2065
|
+
});
|
|
2066
|
+
assertInstanceOf(embedded, Note);
|
|
2067
|
+
assertInstanceOf(
|
|
2068
|
+
await embedded.getAttribution({
|
|
2069
|
+
documentLoader,
|
|
2070
|
+
contextLoader: mockDocumentLoader,
|
|
2071
|
+
gateways: ["https://gw.example"],
|
|
2072
|
+
}),
|
|
2073
|
+
Person,
|
|
2074
|
+
);
|
|
2075
|
+
deepStrictEqual(verifier.calls.length, 3);
|
|
2076
|
+
});
|
|
2077
|
+
|
|
2078
|
+
await t.step("the constructor and clone()", async () => {
|
|
2079
|
+
const verifier = createRecordingVerifier();
|
|
2080
|
+
const activity = new Create({
|
|
2081
|
+
id: new URL(gatewayUrl("https://gw.example", "/activities/1")),
|
|
2082
|
+
object: new URL(compatibleNoteId),
|
|
2083
|
+
}, { contextLoader: mockDocumentLoader, verifyPortableObject: verifier });
|
|
2084
|
+
assertInstanceOf(
|
|
2085
|
+
await activity.getObject({
|
|
2086
|
+
documentLoader,
|
|
2087
|
+
contextLoader: mockDocumentLoader,
|
|
2088
|
+
}),
|
|
2089
|
+
Note,
|
|
2090
|
+
);
|
|
2091
|
+
deepStrictEqual(verifier.calls.length, 1);
|
|
2092
|
+
// A clone does not inherit it, but takes it as an option:
|
|
2093
|
+
const clone = activity.clone({ object: new URL(compatibleNoteId) });
|
|
2094
|
+
await rejects(
|
|
2095
|
+
async () =>
|
|
2096
|
+
await clone.getObject({
|
|
2097
|
+
documentLoader,
|
|
2098
|
+
contextLoader: mockDocumentLoader,
|
|
2099
|
+
}),
|
|
2100
|
+
TypeError,
|
|
2101
|
+
);
|
|
2102
|
+
const clone2 = activity.clone(
|
|
2103
|
+
{ object: new URL(compatibleNoteId) },
|
|
2104
|
+
{ contextLoader: mockDocumentLoader, verifyPortableObject: verifier },
|
|
2105
|
+
);
|
|
2106
|
+
assertInstanceOf(
|
|
2107
|
+
await clone2.getObject({
|
|
2108
|
+
documentLoader,
|
|
2109
|
+
contextLoader: mockDocumentLoader,
|
|
2110
|
+
}),
|
|
2111
|
+
Note,
|
|
2112
|
+
);
|
|
2113
|
+
deepStrictEqual(verifier.calls.length, 2);
|
|
2114
|
+
});
|
|
2115
|
+
|
|
2116
|
+
await t.step("an explicit option applies to that call only", async () => {
|
|
2117
|
+
const stored = createRecordingVerifier();
|
|
2118
|
+
const given = createRecordingVerifier();
|
|
2119
|
+
const activity = await createCompatibleActivity(
|
|
2120
|
+
compatibleNoteId,
|
|
2121
|
+
undefined,
|
|
2122
|
+
{ verifyPortableObject: stored },
|
|
2123
|
+
);
|
|
2124
|
+
const object = await activity.getObject({
|
|
2125
|
+
documentLoader,
|
|
2126
|
+
contextLoader: mockDocumentLoader,
|
|
2127
|
+
verifyPortableObject: given,
|
|
2128
|
+
});
|
|
2129
|
+
assertInstanceOf(object, Note);
|
|
2130
|
+
deepStrictEqual([stored.calls.length, given.calls.length], [0, 1]);
|
|
2131
|
+
// The fetched object inherits the parent's default, not the verifier
|
|
2132
|
+
// given to the call:
|
|
2133
|
+
await object.getAttribution({
|
|
2134
|
+
documentLoader,
|
|
2135
|
+
contextLoader: mockDocumentLoader,
|
|
2136
|
+
gateways: ["https://gw.example"],
|
|
2137
|
+
});
|
|
2138
|
+
deepStrictEqual([stored.calls.length, given.calls.length], [1, 1]);
|
|
2139
|
+
|
|
2140
|
+
// Without a default, the fetched object has none either:
|
|
2141
|
+
const plain = await createCompatibleActivity(compatibleNoteId);
|
|
2142
|
+
const object2 = await plain.getObject({
|
|
2143
|
+
documentLoader,
|
|
2144
|
+
contextLoader: mockDocumentLoader,
|
|
2145
|
+
verifyPortableObject: given,
|
|
2146
|
+
});
|
|
2147
|
+
assertInstanceOf(object2, Note);
|
|
2148
|
+
await rejects(
|
|
2149
|
+
async () =>
|
|
2150
|
+
await object2.getAttribution({
|
|
2151
|
+
documentLoader,
|
|
2152
|
+
contextLoader: mockDocumentLoader,
|
|
2153
|
+
gateways: ["https://gw.example"],
|
|
2154
|
+
}),
|
|
2155
|
+
TypeError,
|
|
2156
|
+
);
|
|
2157
|
+
});
|
|
2158
|
+
|
|
2159
|
+
await t.step("lookupObject()", async () => {
|
|
2160
|
+
const verifier = createRecordingVerifier();
|
|
2161
|
+
const object = await lookupObject(compatibleNoteId, {
|
|
2162
|
+
documentLoader,
|
|
2163
|
+
contextLoader: mockDocumentLoader,
|
|
2164
|
+
verifyPortableObject: verifier,
|
|
2165
|
+
});
|
|
2166
|
+
assertInstanceOf(object, Note);
|
|
2167
|
+
deepStrictEqual(verifier.calls.length, 1);
|
|
2168
|
+
assertInstanceOf(
|
|
2169
|
+
await object.getAttribution({
|
|
2170
|
+
documentLoader,
|
|
2171
|
+
contextLoader: mockDocumentLoader,
|
|
2172
|
+
gateways: ["https://gw.example"],
|
|
2173
|
+
}),
|
|
2174
|
+
Person,
|
|
2175
|
+
);
|
|
2176
|
+
deepStrictEqual(verifier.calls.length, 2);
|
|
2177
|
+
});
|
|
2178
|
+
|
|
2179
|
+
await t.step("property preprocessors", async () => {
|
|
2180
|
+
const verifier = createVerifier();
|
|
2181
|
+
const person = await Person.fromJsonLd({
|
|
2182
|
+
"@context": "https://www.w3.org/ns/activitystreams",
|
|
2183
|
+
id: `ap://${did}/actor`,
|
|
2184
|
+
type: "Person",
|
|
2185
|
+
icon: { type: "Link", href: "https://example.com/icon.png" },
|
|
2186
|
+
}, { contextLoader: mockDocumentLoader, verifyPortableObject: verifier });
|
|
2187
|
+
const icon = await person.getIcon({ documentLoader });
|
|
2188
|
+
assertInstanceOf(icon, Image);
|
|
2189
|
+
ok(
|
|
2190
|
+
(icon as unknown as { _verifyPortableObject: unknown })
|
|
2191
|
+
._verifyPortableObject === verifier,
|
|
2192
|
+
);
|
|
2193
|
+
});
|
|
2194
|
+
});
|
package/src/lookup.ts
CHANGED
|
@@ -186,8 +186,8 @@ export interface LookupObjectOptions {
|
|
|
186
186
|
signal?: AbortSignal;
|
|
187
187
|
|
|
188
188
|
/**
|
|
189
|
-
* The [FEP-ef61]
|
|
190
|
-
* `
|
|
189
|
+
* The [FEP-ef61] policy to apply to portable objects, typically
|
|
190
|
+
* `verifyPortableObject()` from `@fedify/fedify`, which
|
|
191
191
|
* `Context.lookupObject()` uses by default.
|
|
192
192
|
*
|
|
193
193
|
* When it is given, portable `ap:`/`ap+ef61:` identifiers and compatible
|
|
@@ -206,6 +206,9 @@ export interface LookupObjectOptions {
|
|
|
206
206
|
* a portable `@id` are refused as cross-origin objects, even with
|
|
207
207
|
* `crossOrigin: "trust"`.
|
|
208
208
|
*
|
|
209
|
+
* The returned object also uses it by default for dereferencing its
|
|
210
|
+
* properties, as if it were passed to its property accessors.
|
|
211
|
+
*
|
|
209
212
|
* [FEP-ef61]: https://w3id.org/fep/ef61
|
|
210
213
|
* @since 2.4.0
|
|
211
214
|
*/
|
|
@@ -456,6 +459,7 @@ async function lookupObjectInternal(
|
|
|
456
459
|
documentLoader,
|
|
457
460
|
contextLoader: snapshot?.loader ?? options.contextLoader,
|
|
458
461
|
tracerProvider: options.tracerProvider,
|
|
462
|
+
verifyPortableObject: options.verifyPortableObject,
|
|
459
463
|
baseUrl: documentUrl,
|
|
460
464
|
});
|
|
461
465
|
} catch (error) {
|
|
@@ -670,6 +674,7 @@ async function dereference(
|
|
|
670
674
|
documentLoader,
|
|
671
675
|
contextLoader,
|
|
672
676
|
tracerProvider,
|
|
677
|
+
verifyPortableObject: options.verifyPortableObject,
|
|
673
678
|
baseUrl,
|
|
674
679
|
}),
|
|
675
680
|
});
|
package/src/preprocessors.ts
CHANGED