@fedify/vocab 2.3.0-dev.1274 → 2.3.0-dev.1280

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.
@@ -1,6 +1,6 @@
1
1
  import { Temporal } from "@js-temporal/polyfill";
2
2
  globalThis.addEventListener = () => {};
3
- import { C as QuoteRequest, E as Tombstone, S as QuoteAuthorization, T as Source, _ as OrderedCollectionPage, a as Create, b as Place, c as Endpoints, d as Hashtag, f as InteractionPolicy, g as Object$1, h as Note, i as Collection, k as vocab_exports, l as Follow, m as Link, n as Announce, o as CryptographicKey, p as InteractionRule, s as Delete, t as Activity, x as Question, y as Person } from "./vocab-B0Z-tH4q.mjs";
3
+ import { C as QuoteRequest, E as Tombstone, S as QuoteAuthorization, T as Source, _ as OrderedCollectionPage, a as Create, b as Place, c as Endpoints, d as Hashtag, f as InteractionPolicy, g as Object$1, h as Note, i as Collection, k as vocab_exports, l as Follow, m as Link, n as Announce, o as CryptographicKey, p as InteractionRule, s as Delete, t as Activity, x as Question, y as Person } from "./vocab-BBRml5cy.mjs";
4
4
  import { t as assertInstanceOf } from "./utils-CE8Dk5hm.mjs";
5
5
  import { mockDocumentLoader, test } from "@fedify/fixture";
6
6
  import { deepStrictEqual, notDeepStrictEqual, ok, rejects, throws } from "node:assert/strict";
@@ -1192,6 +1192,343 @@ test("Person.fromJsonLd() with relative URLs and baseUrl", async () => {
1192
1192
  baseUrl: new URL("https://example.com")
1193
1193
  })).getIcon())?.url, new URL("https://example.com/avatars/test-avatar.jpg"));
1194
1194
  });
1195
+ test("Object.fromJsonLd() normalizes Link icon to Image", async () => {
1196
+ const icon = await (await Object$1.fromJsonLd({
1197
+ "@context": "https://www.w3.org/ns/activitystreams",
1198
+ "type": "Note",
1199
+ "content": "Hello",
1200
+ "icon": {
1201
+ "type": "Link",
1202
+ "href": "https://example.com/icon.png",
1203
+ "mediaType": "image/png",
1204
+ "name": "Icon",
1205
+ "width": 64,
1206
+ "height": 64
1207
+ }
1208
+ }, {
1209
+ documentLoader: mockDocumentLoader,
1210
+ contextLoader: mockDocumentLoader
1211
+ })).getIcon();
1212
+ deepStrictEqual(icon?.url?.href, "https://example.com/icon.png");
1213
+ deepStrictEqual(icon?.mediaType, "image/png");
1214
+ deepStrictEqual(icon?.names, ["Icon"]);
1215
+ deepStrictEqual(icon?.width, 64);
1216
+ deepStrictEqual(icon?.height, 64);
1217
+ });
1218
+ test("Object.fromJsonLd() normalizes Link image to Image", async () => {
1219
+ const obj = await Object$1.fromJsonLd({
1220
+ "@context": "https://www.w3.org/ns/activitystreams",
1221
+ "type": "Note",
1222
+ "content": "Hello",
1223
+ "image": {
1224
+ "type": "Link",
1225
+ "href": "https://example.com/banner.png",
1226
+ "mediaType": "image/png",
1227
+ "width": 800,
1228
+ "height": 200
1229
+ }
1230
+ }, {
1231
+ documentLoader: mockDocumentLoader,
1232
+ contextLoader: mockDocumentLoader
1233
+ });
1234
+ const images = [];
1235
+ for await (const img of obj.getImages()) images.push(img);
1236
+ deepStrictEqual(images[0]?.url?.href, "https://example.com/banner.png");
1237
+ deepStrictEqual(images[0]?.mediaType, "image/png");
1238
+ deepStrictEqual(images[0]?.width, 800);
1239
+ deepStrictEqual(images[0]?.height, 200);
1240
+ });
1241
+ test("Object.fromJsonLd() normalizes Link icon with relative URL", async () => {
1242
+ deepStrictEqual((await (await Object$1.fromJsonLd({
1243
+ "@context": "https://www.w3.org/ns/activitystreams",
1244
+ "type": "Note",
1245
+ "id": "https://example.com/notes/1",
1246
+ "content": "Hello",
1247
+ "icon": {
1248
+ "type": "Link",
1249
+ "href": "/icons/icon.png"
1250
+ }
1251
+ }, {
1252
+ documentLoader: mockDocumentLoader,
1253
+ contextLoader: mockDocumentLoader
1254
+ })).getIcon())?.url?.href, "https://example.com/icons/icon.png");
1255
+ });
1256
+ test("Object.fromJsonLd() normalizes Link icon with relative id and baseUrl", async () => {
1257
+ const obj = await Object$1.fromJsonLd({
1258
+ "@context": "https://www.w3.org/ns/activitystreams",
1259
+ "type": "Note",
1260
+ "id": "/notes/1",
1261
+ "content": "Hello",
1262
+ "icon": {
1263
+ "type": "Link",
1264
+ "href": "/icons/icon.png"
1265
+ }
1266
+ }, {
1267
+ documentLoader: mockDocumentLoader,
1268
+ contextLoader: mockDocumentLoader,
1269
+ baseUrl: new URL("https://example.com/")
1270
+ });
1271
+ const icon = await obj.getIcon();
1272
+ deepStrictEqual(obj.id?.href, "https://example.com/notes/1");
1273
+ deepStrictEqual(icon?.url?.href, "https://example.com/icons/icon.png");
1274
+ });
1275
+ test("Object.fromJsonLd() decodes Image icon with relative id and baseUrl", async () => {
1276
+ const obj = await Object$1.fromJsonLd({
1277
+ "@context": "https://www.w3.org/ns/activitystreams",
1278
+ "type": "Note",
1279
+ "id": "/notes/1",
1280
+ "content": "Hello",
1281
+ "icon": {
1282
+ "type": "Image",
1283
+ "url": "/icons/icon.png"
1284
+ }
1285
+ }, {
1286
+ documentLoader: mockDocumentLoader,
1287
+ contextLoader: mockDocumentLoader,
1288
+ baseUrl: new URL("https://example.com/")
1289
+ });
1290
+ const icon = await obj.getIcon();
1291
+ deepStrictEqual(obj.id?.href, "https://example.com/notes/1");
1292
+ deepStrictEqual(icon?.url?.href, "https://example.com/icons/icon.png");
1293
+ });
1294
+ test("Object.fromJsonLd() decodes compact icon id with relative id and baseUrl", async () => {
1295
+ const obj = await Object$1.fromJsonLd({
1296
+ "@context": "https://www.w3.org/ns/activitystreams",
1297
+ "type": "Note",
1298
+ "id": "/notes/1",
1299
+ "content": "Hello",
1300
+ "icon": "/icons/icon.png"
1301
+ }, {
1302
+ documentLoader: mockDocumentLoader,
1303
+ contextLoader: mockDocumentLoader,
1304
+ baseUrl: new URL("https://example.com/")
1305
+ });
1306
+ deepStrictEqual(obj.id?.href, "https://example.com/notes/1");
1307
+ deepStrictEqual(obj.iconId?.href, "https://example.com/icons/icon.png");
1308
+ });
1309
+ test("Object.fromJsonLd() resolves compact icon id against document base", async () => {
1310
+ const obj = await Object$1.fromJsonLd({
1311
+ "@context": "https://www.w3.org/ns/activitystreams",
1312
+ "type": "Note",
1313
+ "id": "../notes/1",
1314
+ "content": "Hello",
1315
+ "icon": "icon.png"
1316
+ }, {
1317
+ documentLoader: mockDocumentLoader,
1318
+ contextLoader: mockDocumentLoader,
1319
+ baseUrl: new URL("https://example.com/outbox/page.json")
1320
+ });
1321
+ deepStrictEqual(obj.id?.href, "https://example.com/outbox/notes/1");
1322
+ deepStrictEqual(obj.iconId?.href, "https://example.com/outbox/icon.png");
1323
+ });
1324
+ test("Object.fromJsonLd() skips blank node compact icon id", async () => {
1325
+ const obj = await Object$1.fromJsonLd({
1326
+ "@context": "https://www.w3.org/ns/activitystreams",
1327
+ "type": "Note",
1328
+ "id": "/notes/1",
1329
+ "content": "Hello",
1330
+ "icon": { "@id": "_:b0" }
1331
+ }, {
1332
+ documentLoader: mockDocumentLoader,
1333
+ contextLoader: mockDocumentLoader,
1334
+ baseUrl: new URL("https://example.com/")
1335
+ });
1336
+ deepStrictEqual(obj.id?.href, "https://example.com/notes/1");
1337
+ deepStrictEqual(obj.iconId, null);
1338
+ });
1339
+ test("Object.fromJsonLd() resolves compact icon id against baseUrl for did id", async () => {
1340
+ const obj = await Object$1.fromJsonLd({
1341
+ "@context": "https://www.w3.org/ns/activitystreams",
1342
+ "type": "Note",
1343
+ "id": "did:plc:example",
1344
+ "content": "Hello",
1345
+ "icon": "/icons/icon.png"
1346
+ }, {
1347
+ documentLoader: mockDocumentLoader,
1348
+ contextLoader: mockDocumentLoader,
1349
+ baseUrl: new URL("https://example.com/notes/1")
1350
+ });
1351
+ deepStrictEqual(obj.id?.href, "did:plc:example");
1352
+ deepStrictEqual(obj.iconId?.href, "https://example.com/icons/icon.png");
1353
+ });
1354
+ test("Object.getIcon() resolves relative Link href without id via cached re-parse", async () => {
1355
+ const icon = await (await Object$1.fromJsonLd({
1356
+ "@context": "https://www.w3.org/ns/activitystreams",
1357
+ "type": "Note",
1358
+ "content": "Hello",
1359
+ "icon": {
1360
+ "@context": "https://www.w3.org/ns/activitystreams",
1361
+ "type": "Link",
1362
+ "href": "/icons/star.png",
1363
+ "mediaType": "image/png"
1364
+ }
1365
+ }, {
1366
+ documentLoader: mockDocumentLoader,
1367
+ contextLoader: mockDocumentLoader,
1368
+ baseUrl: new URL("https://example.com/")
1369
+ })).getIcon();
1370
+ deepStrictEqual(icon?.url?.href, "https://example.com/icons/star.png");
1371
+ deepStrictEqual(icon?.mediaType, "image/png");
1372
+ });
1373
+ test("Object.fromJsonLd() resolves Link href against document base, not object id", async () => {
1374
+ const obj = await Object$1.fromJsonLd({
1375
+ "@context": "https://www.w3.org/ns/activitystreams",
1376
+ "type": "Note",
1377
+ "id": "../notes/1",
1378
+ "content": "Hello",
1379
+ "icon": {
1380
+ "type": "Link",
1381
+ "href": "icon.png"
1382
+ }
1383
+ }, {
1384
+ documentLoader: mockDocumentLoader,
1385
+ contextLoader: mockDocumentLoader,
1386
+ baseUrl: new URL("https://example.com/outbox/page.json")
1387
+ });
1388
+ const icon = await obj.getIcon();
1389
+ deepStrictEqual(obj.id?.href, "https://example.com/outbox/notes/1");
1390
+ deepStrictEqual(icon?.url?.href, "https://example.com/outbox/icon.png");
1391
+ });
1392
+ test("Object.getIcon() resolves cached relative Link href against baseUrl for did id", async () => {
1393
+ const obj = await Object$1.fromJsonLd({
1394
+ "@context": "https://www.w3.org/ns/activitystreams",
1395
+ "type": "Note",
1396
+ "id": "did:plc:example",
1397
+ "content": "Hello",
1398
+ "icon": {
1399
+ "@context": "https://www.w3.org/ns/activitystreams",
1400
+ "type": "Link",
1401
+ "href": "/icons/star.png",
1402
+ "mediaType": "image/png"
1403
+ }
1404
+ }, {
1405
+ documentLoader: mockDocumentLoader,
1406
+ contextLoader: mockDocumentLoader,
1407
+ baseUrl: new URL("https://example.com/notes/1")
1408
+ });
1409
+ const icon = await obj.getIcon();
1410
+ deepStrictEqual(obj.id?.href, "did:plc:example");
1411
+ deepStrictEqual(icon?.url?.href, "https://example.com/icons/star.png");
1412
+ deepStrictEqual(icon?.mediaType, "image/png");
1413
+ });
1414
+ test("Object.getIcon() ignores mutation of caller's baseUrl after fromJsonLd()", async () => {
1415
+ const origBaseUrl = new URL("https://example.com/");
1416
+ const obj = await Object$1.fromJsonLd({
1417
+ "@context": "https://www.w3.org/ns/activitystreams",
1418
+ "type": "Note",
1419
+ "content": "Hello",
1420
+ "icon": {
1421
+ "@context": "https://www.w3.org/ns/activitystreams",
1422
+ "type": "Link",
1423
+ "href": "/icons/star.png",
1424
+ "mediaType": "image/png"
1425
+ }
1426
+ }, {
1427
+ documentLoader: mockDocumentLoader,
1428
+ contextLoader: mockDocumentLoader,
1429
+ baseUrl: origBaseUrl
1430
+ });
1431
+ origBaseUrl.href = "https://attacker.example/";
1432
+ const icon = await obj.getIcon();
1433
+ deepStrictEqual(icon?.url?.href, "https://example.com/icons/star.png");
1434
+ deepStrictEqual(icon?.mediaType, "image/png");
1435
+ });
1436
+ test("Object.fromJsonLd() does not resolve blank node @id against baseUrl", async () => {
1437
+ deepStrictEqual((await Object$1.fromJsonLd({
1438
+ "@context": "https://www.w3.org/ns/activitystreams",
1439
+ "type": "Note",
1440
+ "id": "_:b0"
1441
+ }, {
1442
+ documentLoader: mockDocumentLoader,
1443
+ contextLoader: mockDocumentLoader,
1444
+ baseUrl: new URL("https://example.com/")
1445
+ })).id, null);
1446
+ });
1447
+ test("Object.fromJsonLd() handles blank node @id without baseUrl", () => {
1448
+ return Object$1.fromJsonLd({
1449
+ "@context": "https://www.w3.org/ns/activitystreams",
1450
+ "type": "Note",
1451
+ "id": "_:b0"
1452
+ }, {
1453
+ documentLoader: mockDocumentLoader,
1454
+ contextLoader: mockDocumentLoader
1455
+ }).then((o) => deepStrictEqual(o.id, null));
1456
+ });
1457
+ test("Object.getAttachments() resolves relative url via stored _baseUrl", async () => {
1458
+ const obj = await Object$1.fromJsonLd({
1459
+ "@context": "https://www.w3.org/ns/activitystreams",
1460
+ "type": "Note",
1461
+ "content": "Hello",
1462
+ "attachment": {
1463
+ "@context": "https://www.w3.org/ns/activitystreams",
1464
+ "type": "Document",
1465
+ "url": "/files/report.pdf",
1466
+ "mediaType": "application/pdf"
1467
+ }
1468
+ }, {
1469
+ documentLoader: mockDocumentLoader,
1470
+ contextLoader: mockDocumentLoader,
1471
+ baseUrl: new URL("https://example.com/")
1472
+ });
1473
+ const attachments = [];
1474
+ for await (const a of obj.getAttachments()) attachments.push(a);
1475
+ deepStrictEqual(attachments.length, 1);
1476
+ const doc = attachments[0];
1477
+ deepStrictEqual(doc?.url?.href, "https://example.com/files/report.pdf");
1478
+ deepStrictEqual(doc?.mediaType, "application/pdf");
1479
+ });
1480
+ test("Object.fromJsonLd() normalizes multiple Link icons", async () => {
1481
+ const obj = await Object$1.fromJsonLd({
1482
+ "@context": "https://www.w3.org/ns/activitystreams",
1483
+ "type": "Note",
1484
+ "content": "Hello",
1485
+ "icon": [{
1486
+ "type": "Link",
1487
+ "href": "https://example.com/a.png"
1488
+ }, {
1489
+ "type": "Image",
1490
+ "url": "https://example.com/b.png"
1491
+ }]
1492
+ }, {
1493
+ documentLoader: mockDocumentLoader,
1494
+ contextLoader: mockDocumentLoader
1495
+ });
1496
+ const icons = [];
1497
+ for await (const i of obj.getIcons()) icons.push(i);
1498
+ deepStrictEqual(icons.length, 2);
1499
+ deepStrictEqual(icons[0]?.url?.href, "https://example.com/a.png");
1500
+ deepStrictEqual(icons[1]?.url?.href, "https://example.com/b.png");
1501
+ });
1502
+ test("Object.getIcon() normalizes fetched Link document to Image", async () => {
1503
+ const linkDocUrl = "https://example.com/icons/avatar-link";
1504
+ const linkDoc = {
1505
+ "@context": "https://www.w3.org/ns/activitystreams",
1506
+ type: "Link",
1507
+ href: "https://example.com/avatars/user.png",
1508
+ mediaType: "image/png",
1509
+ width: 128,
1510
+ height: 128
1511
+ };
1512
+ const docLoader = async (url) => {
1513
+ if (url === linkDocUrl) return {
1514
+ document: linkDoc,
1515
+ documentUrl: url,
1516
+ contextUrl: null
1517
+ };
1518
+ return await mockDocumentLoader(url);
1519
+ };
1520
+ const icon = await new Person({
1521
+ id: new URL("https://example.com/ap/actors/test-user"),
1522
+ icon: new URL(linkDocUrl)
1523
+ }).getIcon({
1524
+ documentLoader: docLoader,
1525
+ contextLoader: mockDocumentLoader
1526
+ });
1527
+ deepStrictEqual(icon?.url?.href, "https://example.com/avatars/user.png");
1528
+ deepStrictEqual(icon?.mediaType, "image/png");
1529
+ deepStrictEqual(icon?.width, 128);
1530
+ deepStrictEqual(icon?.height, 128);
1531
+ });
1195
1532
  test("FEP-fe34: Trust tracking in object construction", async () => {
1196
1533
  const note = new Note({
1197
1534
  id: new URL("https://example.com/note"),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fedify/vocab",
3
- "version": "2.3.0-dev.1274+8e82de77",
3
+ "version": "2.3.0-dev.1280+7bca39c7",
4
4
  "homepage": "https://fedify.dev/",
5
5
  "repository": {
6
6
  "type": "git",
@@ -46,9 +46,9 @@
46
46
  "es-toolkit": "1.46.1",
47
47
  "jsonld": "^9.0.0",
48
48
  "pkijs": "^3.3.3",
49
- "@fedify/vocab-tools": "2.3.0-dev.1274+8e82de77",
50
- "@fedify/webfinger": "2.3.0-dev.1274+8e82de77",
51
- "@fedify/vocab-runtime": "2.3.0-dev.1274+8e82de77"
49
+ "@fedify/vocab-tools": "2.3.0-dev.1280+7bca39c7",
50
+ "@fedify/vocab-runtime": "2.3.0-dev.1280+7bca39c7",
51
+ "@fedify/webfinger": "2.3.0-dev.1280+7bca39c7"
52
52
  },
53
53
  "devDependencies": {
54
54
  "@types/node": "^22.17.0",
package/src/mod.ts CHANGED
@@ -53,6 +53,7 @@ export * from "./actor.ts";
53
53
  export * from "./constants.ts";
54
54
  export * from "./handle.ts";
55
55
  export * from "./lookup.ts";
56
+ export * from "./preprocessors.ts";
56
57
  export * from "./type.ts";
57
58
  export * from "./vocab.ts";
58
59
  export { LanguageString } from "@fedify/vocab-runtime";
package/src/object.yaml CHANGED
@@ -137,6 +137,9 @@ properties:
137
137
  (vertical) and should be suitable for presentation at a small size.
138
138
  range:
139
139
  - "https://www.w3.org/ns/activitystreams#Image"
140
+ preprocessors:
141
+ - module: ./preprocessors.ts
142
+ function: normalizeLinkToImage
140
143
 
141
144
  - pluralName: images
142
145
  singularName: image
@@ -149,6 +152,9 @@ properties:
149
152
  limitations assumed.
150
153
  range:
151
154
  - "https://www.w3.org/ns/activitystreams#Image"
155
+ preprocessors:
156
+ - module: ./preprocessors.ts
157
+ function: normalizeLinkToImage
152
158
 
153
159
  - pluralName: replyTargets
154
160
  singularName: replyTarget
@@ -0,0 +1,48 @@
1
+ import type { PropertyPreprocessor } from "@fedify/vocab-runtime";
2
+ import { Image, Link } from "./vocab.ts";
3
+
4
+ /**
5
+ * A property preprocessor that normalizes Link values to Image objects.
6
+ *
7
+ * When an `icon` or `image` property on a vocabulary object contains an
8
+ * explicit ActivityStreams `Link` rather than an `Image`, this preprocessor
9
+ * converts it into an `Image` by mapping `href` to `url`, copying
10
+ * `mediaType`, `name`, `width`, and `height`, and discarding the rest.
11
+ *
12
+ * If the value is not a Link, or the Link has no `href`, it returns
13
+ * `undefined` so the normal range decoder continues.
14
+ * @since 2.3.0
15
+ */
16
+ export const normalizeLinkToImage: PropertyPreprocessor<Image> = async (
17
+ value,
18
+ context,
19
+ ) => {
20
+ if (
21
+ typeof value !== "object" ||
22
+ value === null ||
23
+ Array.isArray(value) ||
24
+ !("@type" in value) ||
25
+ !Array.isArray(value["@type"]) ||
26
+ !value["@type"].includes("https://www.w3.org/ns/activitystreams#Link")
27
+ ) {
28
+ return undefined;
29
+ }
30
+
31
+ let link: Link;
32
+ try {
33
+ link = await Link.fromJsonLd(value, context);
34
+ } catch (error) {
35
+ return error instanceof Error ? error : new Error(String(error));
36
+ }
37
+
38
+ if (link.href == null) return undefined;
39
+
40
+ return new Image({
41
+ id: link.id,
42
+ url: link.href,
43
+ mediaType: link.mediaType,
44
+ names: link.names != null && link.names.length > 0 ? link.names : undefined,
45
+ width: link.width,
46
+ height: link.height,
47
+ });
48
+ };