@fedify/vocab 2.4.0-pr.936.41 → 2.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/deno.json +1 -1
- package/dist/mod.cjs +32014 -9323
- package/dist/mod.d.cts +4770 -91
- package/dist/mod.d.ts +4770 -91
- package/dist/mod.js +31907 -9223
- package/dist-tests/actor-D_QaCMro.mjs +276 -0
- package/dist-tests/actor.test.mjs +55 -49
- package/dist-tests/{actor-MBpzF3-D.mjs → esm-YWo4aQQe.mjs} +15 -211
- package/dist-tests/gateway.test.mjs +1906 -0
- package/dist-tests/lookup-BevjwZhB.mjs +480 -0
- package/dist-tests/lookup.test.mjs +10 -268
- package/dist-tests/object.yaml +15 -0
- package/dist-tests/portable-lookup.test.mjs +653 -0
- package/dist-tests/signed-representation.test.mjs +189 -0
- package/dist-tests/translation.test.mjs +404 -0
- package/dist-tests/translation.yaml +91 -0
- package/dist-tests/type.test.mjs +2 -2
- package/dist-tests/{vocab-Bjwctrxn.mjs → vocab-DaniQwU7.mjs} +31669 -9267
- package/dist-tests/vocab.test.mjs +901 -11
- package/package.json +5 -5
- package/src/__snapshots__/vocab.test.ts.snap +291 -15
- package/src/actor.test.ts +41 -40
- package/src/actor.ts +120 -1
- package/src/gateway.test.ts +2637 -0
- package/src/lookup.test.ts +6 -5
- package/src/lookup.ts +460 -33
- package/src/mod.ts +9 -0
- package/src/object.yaml +15 -0
- package/src/portable-lookup.test.ts +889 -0
- package/src/preprocessors.ts +1 -1
- package/src/signed-representation.test.ts +277 -0
- package/src/translation.test.ts +518 -0
- package/src/translation.yaml +91 -0
- package/src/vocab.test.ts +1219 -5
- /package/dist-tests/{type-_BlsfT3S.mjs → type-BMdjm4Eg.mjs} +0 -0
- /package/dist-tests/{utils-CQjrpUkt.mjs → utils-Df2odC27.mjs} +0 -0
|
@@ -1,269 +1,11 @@
|
|
|
1
|
-
import
|
|
1
|
+
import "temporal-polyfill";
|
|
2
2
|
globalThis.addEventListener = () => {};
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import { t as
|
|
6
|
-
import { t as assertInstanceOf } from "./utils-
|
|
3
|
+
import { A as Object$1, F as Person, k as Note, s as Collection } from "./vocab-DaniQwU7.mjs";
|
|
4
|
+
import { t as esm_default } from "./esm-YWo4aQQe.mjs";
|
|
5
|
+
import { n as traverseCollection, t as lookupObject } from "./lookup-BevjwZhB.mjs";
|
|
6
|
+
import { t as assertInstanceOf } from "./utils-Df2odC27.mjs";
|
|
7
7
|
import { createTestMeterProvider, createTestTracerProvider, mockDocumentLoader, test } from "@fedify/fixture";
|
|
8
8
|
import { deepStrictEqual, equal, ok, rejects } from "node:assert/strict";
|
|
9
|
-
import { lookupWebFinger } from "@fedify/webfinger";
|
|
10
|
-
import { SpanStatusCode, trace } from "@opentelemetry/api";
|
|
11
|
-
import { getLogger } from "@logtape/logtape";
|
|
12
|
-
import { getDocumentLoader, haveSameFe34Origin, haveSameIriOrigin, parseIri } from "@fedify/vocab-runtime";
|
|
13
|
-
import { delay } from "es-toolkit";
|
|
14
|
-
//#region src/handle.ts
|
|
15
|
-
/**
|
|
16
|
-
* Regular expression to match a fediverse handle in the format `@user@server`
|
|
17
|
-
* or `user@server`. The `user` part can contain alphanumeric characters and
|
|
18
|
-
* some special characters except `@`. The `server` part is all characters
|
|
19
|
-
* after the `@` symbol in the middle.
|
|
20
|
-
*/
|
|
21
|
-
const handleRegexp = /^@?((?:[-A-Za-z0-9._~!$&'()*+,;=]|%[A-Fa-f0-9]{2})+)@([^@]+)$/;
|
|
22
|
-
/**
|
|
23
|
-
* Parses a fediverse handle in the format `@user@server` or `user@server`.
|
|
24
|
-
* The `user` part can contain alphanumeric characters and some special
|
|
25
|
-
* characters except `@`. The `server` part is all characters after the `@`
|
|
26
|
-
* symbol in the middle.
|
|
27
|
-
*
|
|
28
|
-
* @example
|
|
29
|
-
* ```typescript
|
|
30
|
-
* const handle = parseFediverseHandle("@username@example.com");
|
|
31
|
-
* console.log(handle?.username); // "username"
|
|
32
|
-
* console.log(handle?.host); // "example.com"
|
|
33
|
-
* ```
|
|
34
|
-
*
|
|
35
|
-
* @param handle - The fediverse handle string to parse.
|
|
36
|
-
* @returns A {@link FediverseHandle} object with `username` and `host`
|
|
37
|
-
* if the input is valid; otherwise `null`.
|
|
38
|
-
* @since 1.8.0
|
|
39
|
-
*/
|
|
40
|
-
function parseFediverseHandle(handle) {
|
|
41
|
-
const match = handleRegexp.exec(handle);
|
|
42
|
-
if (match) return {
|
|
43
|
-
username: match[1],
|
|
44
|
-
host: match[2]
|
|
45
|
-
};
|
|
46
|
-
return null;
|
|
47
|
-
}
|
|
48
|
-
/**
|
|
49
|
-
* Converts a fediverse handle in the format `@user@server` or `user@server`
|
|
50
|
-
* to an `acct:` URI, which is a URL-like identifier for ActivityPub actors.
|
|
51
|
-
*
|
|
52
|
-
* @example
|
|
53
|
-
* ```typescript
|
|
54
|
-
* const identifier = toAcctUrl("@username@example.com");
|
|
55
|
-
* console.log(identifier?.href); // "acct:username@example.com"
|
|
56
|
-
* ```
|
|
57
|
-
*
|
|
58
|
-
* @param handle - The fediverse handle string to convert.
|
|
59
|
-
* @returns A `URL` object representing the `acct:` URI if conversion succeeds;
|
|
60
|
-
* otherwise `null`.
|
|
61
|
-
* @since 1.8.0
|
|
62
|
-
*/
|
|
63
|
-
function toAcctUrl(handle) {
|
|
64
|
-
const parsed = parseFediverseHandle(handle);
|
|
65
|
-
if (!parsed) return null;
|
|
66
|
-
return new URL(`acct:${parsed.username}@${parsed.host}`);
|
|
67
|
-
}
|
|
68
|
-
//#endregion
|
|
69
|
-
//#region src/lookup.ts
|
|
70
|
-
const objectLookupCounters = /* @__PURE__ */ new WeakMap();
|
|
71
|
-
function getObjectLookupCounter(meterProvider) {
|
|
72
|
-
let counter = objectLookupCounters.get(meterProvider);
|
|
73
|
-
if (counter == null) {
|
|
74
|
-
counter = meterProvider.getMeter(name, version).createCounter("activitypub.object.lookup", {
|
|
75
|
-
description: "ActivityStreams object lookups via lookupObject(), classified by whether the resolved value is an Actor.",
|
|
76
|
-
unit: "{lookup}"
|
|
77
|
-
});
|
|
78
|
-
objectLookupCounters.set(meterProvider, counter);
|
|
79
|
-
}
|
|
80
|
-
return counter;
|
|
81
|
-
}
|
|
82
|
-
function getLookupRemoteHost(identifier) {
|
|
83
|
-
let url;
|
|
84
|
-
if (identifier instanceof URL) url = identifier;
|
|
85
|
-
else try {
|
|
86
|
-
url = new URL(identifier);
|
|
87
|
-
} catch {
|
|
88
|
-
return extractHandleHost(identifier.startsWith("@") ? identifier.slice(1) : identifier);
|
|
89
|
-
}
|
|
90
|
-
if (url.host !== "") return url.host;
|
|
91
|
-
if (url.protocol === "acct:") return extractHandleHost(url.pathname);
|
|
92
|
-
}
|
|
93
|
-
function extractHandleHost(handle) {
|
|
94
|
-
const at = handle.lastIndexOf("@");
|
|
95
|
-
if (at < 0 || at >= handle.length - 1) return void 0;
|
|
96
|
-
const candidate = handle.slice(at + 1);
|
|
97
|
-
if (/[/?#\s]/.test(candidate)) return void 0;
|
|
98
|
-
try {
|
|
99
|
-
return new URL(`https://${candidate}`).host || void 0;
|
|
100
|
-
} catch {
|
|
101
|
-
return;
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
const logger = getLogger([
|
|
105
|
-
"fedify",
|
|
106
|
-
"vocab",
|
|
107
|
-
"lookup"
|
|
108
|
-
]);
|
|
109
|
-
/**
|
|
110
|
-
* Looks up an ActivityStreams object by its URI (including `acct:` URIs)
|
|
111
|
-
* or a fediverse handle (e.g., `@user@server` or `user@server`).
|
|
112
|
-
*
|
|
113
|
-
* @example
|
|
114
|
-
* ``` typescript
|
|
115
|
-
* // Look up an actor by its fediverse handle:
|
|
116
|
-
* await lookupObject("@hongminhee@fosstodon.org");
|
|
117
|
-
* // returning a `Person` object.
|
|
118
|
-
*
|
|
119
|
-
* // A fediverse handle can omit the leading '@':
|
|
120
|
-
* await lookupObject("hongminhee@fosstodon.org");
|
|
121
|
-
* // returning a `Person` object.
|
|
122
|
-
*
|
|
123
|
-
* // A `acct:` URI can be used as well:
|
|
124
|
-
* await lookupObject("acct:hongminhee@fosstodon.org");
|
|
125
|
-
* // returning a `Person` object.
|
|
126
|
-
*
|
|
127
|
-
* // Look up an object by its URI:
|
|
128
|
-
* await lookupObject("https://todon.eu/@hongminhee/112060633798771581");
|
|
129
|
-
* // returning a `Note` object.
|
|
130
|
-
*
|
|
131
|
-
* // It can be a `URL` object as well:
|
|
132
|
-
* await lookupObject(new URL("https://todon.eu/@hongminhee/112060633798771581"));
|
|
133
|
-
* // returning a `Note` object.
|
|
134
|
-
* ```
|
|
135
|
-
*
|
|
136
|
-
* @param identifier The URI or fediverse handle to look up.
|
|
137
|
-
* @param options Lookup options.
|
|
138
|
-
* @returns The object, or `null` if not found.
|
|
139
|
-
* @since 0.2.0
|
|
140
|
-
*/
|
|
141
|
-
async function lookupObject(identifier, options = {}) {
|
|
142
|
-
return await (options.tracerProvider ?? trace.getTracerProvider()).getTracer(name, version).startActiveSpan("activitypub.lookup_object", async (span) => {
|
|
143
|
-
let kind = "other";
|
|
144
|
-
try {
|
|
145
|
-
const result = await lookupObjectInternal(identifier, options);
|
|
146
|
-
if (result != null) kind = isActor(result) ? "actor" : "object";
|
|
147
|
-
if (result == null) span.setStatus({ code: SpanStatusCode.ERROR });
|
|
148
|
-
else {
|
|
149
|
-
if (result.id != null) span.setAttribute("activitypub.object.id", result.id.href);
|
|
150
|
-
span.setAttribute("activitypub.object.type", getTypeId(result).href);
|
|
151
|
-
if (result.replyTargetIds.length > 0) span.setAttribute("activitypub.object.in_reply_to", result.replyTargetIds.map((id) => id.href));
|
|
152
|
-
span.addEvent("activitypub.object.fetched", {
|
|
153
|
-
"activitypub.object.type": getTypeId(result).href,
|
|
154
|
-
"activitypub.object.json": JSON.stringify(await result.toJsonLd(options))
|
|
155
|
-
});
|
|
156
|
-
}
|
|
157
|
-
return result;
|
|
158
|
-
} catch (error) {
|
|
159
|
-
span.recordException(error);
|
|
160
|
-
span.setStatus({
|
|
161
|
-
code: SpanStatusCode.ERROR,
|
|
162
|
-
message: String(error)
|
|
163
|
-
});
|
|
164
|
-
throw error;
|
|
165
|
-
} finally {
|
|
166
|
-
if (options.meterProvider != null) {
|
|
167
|
-
const attributes = { "activitypub.lookup.kind": kind };
|
|
168
|
-
const host = getLookupRemoteHost(identifier);
|
|
169
|
-
if (host != null) attributes["activitypub.remote.host"] = host;
|
|
170
|
-
getObjectLookupCounter(options.meterProvider).add(1, attributes);
|
|
171
|
-
}
|
|
172
|
-
span.end();
|
|
173
|
-
}
|
|
174
|
-
});
|
|
175
|
-
}
|
|
176
|
-
async function lookupObjectInternal(identifier, options = {}) {
|
|
177
|
-
const documentLoader = options.documentLoader ?? getDocumentLoader({ userAgent: options.userAgent });
|
|
178
|
-
if (typeof identifier === "string") identifier = toAcctUrl(identifier) ?? new URL(identifier);
|
|
179
|
-
let remoteDoc = null;
|
|
180
|
-
if (identifier.protocol === "http:" || identifier.protocol === "https:") try {
|
|
181
|
-
remoteDoc = await documentLoader(identifier.href, { signal: options.signal });
|
|
182
|
-
} catch (error) {
|
|
183
|
-
logger.debug("Failed to fetch remote document:\n{error}", { error });
|
|
184
|
-
}
|
|
185
|
-
if (remoteDoc == null) {
|
|
186
|
-
const jrd = await lookupWebFinger(identifier, {
|
|
187
|
-
userAgent: options.userAgent,
|
|
188
|
-
tracerProvider: options.tracerProvider,
|
|
189
|
-
meterProvider: options.meterProvider,
|
|
190
|
-
allowPrivateAddress: "allowPrivateAddress" in options && options.allowPrivateAddress === true,
|
|
191
|
-
signal: options.signal
|
|
192
|
-
});
|
|
193
|
-
if (jrd?.links == null) return null;
|
|
194
|
-
for (const l of jrd.links) {
|
|
195
|
-
if (l.type !== "application/activity+json" && !l.type?.match(/application\/ld\+json;\s*profile="https:\/\/www.w3.org\/ns\/activitystreams"/) || l.rel !== "self" || l.href == null) continue;
|
|
196
|
-
try {
|
|
197
|
-
remoteDoc = await documentLoader(l.href, { signal: options.signal });
|
|
198
|
-
break;
|
|
199
|
-
} catch (error) {
|
|
200
|
-
logger.debug("Failed to fetch remote document:\n{error}", { error });
|
|
201
|
-
continue;
|
|
202
|
-
}
|
|
203
|
-
}
|
|
204
|
-
}
|
|
205
|
-
if (remoteDoc == null) return null;
|
|
206
|
-
let object;
|
|
207
|
-
let documentUrl;
|
|
208
|
-
try {
|
|
209
|
-
documentUrl = parseIri(remoteDoc.documentUrl);
|
|
210
|
-
object = await Object$1.fromJsonLd(remoteDoc.document, {
|
|
211
|
-
documentLoader,
|
|
212
|
-
contextLoader: options.contextLoader,
|
|
213
|
-
tracerProvider: options.tracerProvider,
|
|
214
|
-
baseUrl: documentUrl
|
|
215
|
-
});
|
|
216
|
-
} catch (error) {
|
|
217
|
-
if (error instanceof TypeError) {
|
|
218
|
-
logger.debug("Failed to parse JSON-LD document: {error}\n{document}", {
|
|
219
|
-
...remoteDoc,
|
|
220
|
-
error
|
|
221
|
-
});
|
|
222
|
-
return null;
|
|
223
|
-
}
|
|
224
|
-
throw error;
|
|
225
|
-
}
|
|
226
|
-
if (options.crossOrigin !== "trust" && object.id != null && !haveSameIriOrigin(object.id, documentUrl) && !haveSameFe34Origin(object.id, documentUrl)) {
|
|
227
|
-
if (options.crossOrigin === "throw") throw new Error(`The object's @id (${object.id.href}) has a different origin than the document URL (${remoteDoc.documentUrl}); refusing to return the object. If you want to bypass this check and are aware of the security implications, set the crossOrigin option to "trust".`);
|
|
228
|
-
logger.warn("The object's @id ({objectId}) has a different origin than the document URL ({documentUrl}); refusing to return the object. If you want to bypass this check and are aware of the security implications, set the crossOrigin option to \"trust\".", {
|
|
229
|
-
...remoteDoc,
|
|
230
|
-
objectId: object.id.href
|
|
231
|
-
});
|
|
232
|
-
return null;
|
|
233
|
-
}
|
|
234
|
-
return object;
|
|
235
|
-
}
|
|
236
|
-
/**
|
|
237
|
-
* Traverses a collection, yielding each item in the collection.
|
|
238
|
-
* If the collection is paginated, it will fetch the next page
|
|
239
|
-
* automatically.
|
|
240
|
-
*
|
|
241
|
-
* @example
|
|
242
|
-
* ``` typescript
|
|
243
|
-
* const collection = await lookupObject(collectionUrl);
|
|
244
|
-
* if (collection instanceof Collection) {
|
|
245
|
-
* for await (const item of traverseCollection(collection)) {
|
|
246
|
-
* console.log(item.id?.href);
|
|
247
|
-
* }
|
|
248
|
-
* }
|
|
249
|
-
* ```
|
|
250
|
-
*
|
|
251
|
-
* @param collection The collection to traverse.
|
|
252
|
-
* @param options Options for traversing the collection.
|
|
253
|
-
* @returns An async iterable of each item in the collection.
|
|
254
|
-
* @since 1.1.0
|
|
255
|
-
*/
|
|
256
|
-
async function* traverseCollection(collection, options = {}) {
|
|
257
|
-
const interval = Temporal.Duration.from(options.interval ?? { seconds: 0 }).total("millisecond");
|
|
258
|
-
let page = await collection.getFirst(options);
|
|
259
|
-
if (page == null) for await (const item of collection.getItems(options)) yield item;
|
|
260
|
-
else while (page != null) {
|
|
261
|
-
for await (const item of page.getItems(options)) yield item;
|
|
262
|
-
if (interval > 0) await delay(interval);
|
|
263
|
-
page = await page.getNext(options);
|
|
264
|
-
}
|
|
265
|
-
}
|
|
266
|
-
//#endregion
|
|
267
9
|
//#region src/lookup.test.ts
|
|
268
10
|
test("lookupObject()", {
|
|
269
11
|
sanitizeResources: false,
|
|
@@ -316,7 +58,7 @@ test("lookupObject()", {
|
|
|
316
58
|
name: "Hong Gildong"
|
|
317
59
|
}));
|
|
318
60
|
});
|
|
319
|
-
esm_default.removeRoutes();
|
|
61
|
+
esm_default.removeRoutes({ includeFallback: false });
|
|
320
62
|
esm_default.get("begin:https://example.com/.well-known/webfinger", {
|
|
321
63
|
subject: "acct:janedoe@example.com",
|
|
322
64
|
links: [{
|
|
@@ -329,7 +71,7 @@ test("lookupObject()", {
|
|
|
329
71
|
deepStrictEqual(await lookupObject("janedoe@example.com", options), null);
|
|
330
72
|
deepStrictEqual(await lookupObject("https://example.com/404", options), null);
|
|
331
73
|
});
|
|
332
|
-
esm_default.removeRoutes();
|
|
74
|
+
esm_default.removeRoutes({ includeFallback: false });
|
|
333
75
|
esm_default.get("begin:https://example.com/.well-known/webfinger", () => new Promise((resolve) => {
|
|
334
76
|
setTimeout(() => {
|
|
335
77
|
resolve({
|
|
@@ -351,7 +93,7 @@ test("lookupObject()", {
|
|
|
351
93
|
controller.abort();
|
|
352
94
|
deepStrictEqual(await promise, null);
|
|
353
95
|
});
|
|
354
|
-
esm_default.removeRoutes();
|
|
96
|
+
esm_default.removeRoutes({ includeFallback: false });
|
|
355
97
|
esm_default.get("begin:https://example.com/.well-known/webfinger", {
|
|
356
98
|
subject: "acct:johndoe@example.com",
|
|
357
99
|
links: [{
|
|
@@ -369,7 +111,7 @@ test("lookupObject()", {
|
|
|
369
111
|
assertInstanceOf(person, Person);
|
|
370
112
|
deepStrictEqual(person.id, new URL("https://example.com/person"));
|
|
371
113
|
});
|
|
372
|
-
esm_default.removeRoutes();
|
|
114
|
+
esm_default.removeRoutes({ includeFallback: false });
|
|
373
115
|
esm_default.get("begin:https://example.com/.well-known/webfinger", () => new Promise((resolve) => {
|
|
374
116
|
setTimeout(() => {
|
|
375
117
|
resolve({
|
|
@@ -390,7 +132,7 @@ test("lookupObject()", {
|
|
|
390
132
|
signal: controller.signal
|
|
391
133
|
}), null);
|
|
392
134
|
});
|
|
393
|
-
esm_default.removeRoutes();
|
|
135
|
+
esm_default.removeRoutes({ includeFallback: false });
|
|
394
136
|
esm_default.get("https://example.com/slow-object", () => new Promise((resolve) => {
|
|
395
137
|
setTimeout(() => {
|
|
396
138
|
resolve({
|
package/dist-tests/object.yaml
CHANGED
|
@@ -469,3 +469,18 @@ properties:
|
|
|
469
469
|
(boost) was approved by the target post's author.
|
|
470
470
|
range:
|
|
471
471
|
- "https://gotosocial.org/ns#AnnounceAuthorization"
|
|
472
|
+
|
|
473
|
+
- pluralName: translations
|
|
474
|
+
singularName: translation
|
|
475
|
+
compactName: translations
|
|
476
|
+
uri: "https://w3id.org/fep/22cd#translations"
|
|
477
|
+
extraContext: "https://w3id.org/fep/22cd"
|
|
478
|
+
description: |
|
|
479
|
+
Translation metadata for the language versions in this object's content,
|
|
480
|
+
name, or summary maps, as defined by the draft
|
|
481
|
+
[FEP-22cd](https://w3id.org/fep/22cd). Each entry describes one translated
|
|
482
|
+
language; directly authored languages have no entry. The entries share
|
|
483
|
+
this object's identity, replies, and reactions. Translator credit grants
|
|
484
|
+
no authority to update the object.
|
|
485
|
+
range:
|
|
486
|
+
- "https://w3id.org/fep/22cd#Translation"
|