@fedify/fedify 2.4.0-dev.2149 → 2.4.0-dev.2157
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/dist/{builder-W2fIPRjv.mjs → builder-CLaS4-yA.mjs} +56 -3
- package/dist/{client-DpnpgjyX.mjs → client-CEnNz8O7.mjs} +7 -3
- package/dist/compat/mod.d.cts +1 -1
- package/dist/compat/mod.d.ts +1 -1
- package/dist/compat/transformers.test.mjs +1 -1
- package/dist/{compound-proof-B1QH25S6.mjs → compound-proof-D7K94KPz.mjs} +216 -4
- package/dist/{context-DfSVPcJ8.mjs → context-B2rlkkyJ.mjs} +9 -2
- package/dist/{context-CrDDVrbF.d.cts → context-BMggDR-l.d.cts} +394 -5
- package/dist/{context-B2C8nuN3.d.ts → context-exYu7gDU.d.ts} +394 -5
- package/dist/{deno-yXpYZm4K.mjs → deno-D1gcWWqB.mjs} +1 -1
- package/dist/{dist-BQvArojP.mjs → dist-DJQM4u7i.mjs} +2 -1
- package/dist/docloader-BMqr3hji.mjs +79 -0
- package/dist/federation/builder.test.mjs +1 -1
- package/dist/federation/compound-fanout.test.mjs +3 -3
- package/dist/federation/handler-compound-proof.test.mjs +4 -4
- package/dist/federation/handler.test.mjs +6 -6
- package/dist/federation/hashlink-media.test.mjs +2 -2
- package/dist/federation/idempotency.test.mjs +2 -2
- package/dist/federation/metrics.test.mjs +1 -1
- package/dist/federation/middleware.test.mjs +187 -10
- package/dist/federation/mod.cjs +1 -1
- package/dist/federation/mod.d.cts +3 -3
- package/dist/federation/mod.d.ts +3 -3
- package/dist/federation/mod.js +1 -1
- package/dist/federation/mq.test.mjs +98 -69
- package/dist/federation/outgoing-proof.test.mjs +5 -5
- package/dist/federation/portable-actor.test.mjs +3 -3
- package/dist/federation/portable-chain.test.mjs +3 -3
- package/dist/federation/portable-collection.test.d.mts +2 -0
- package/dist/federation/portable-collection.test.mjs +768 -0
- package/dist/federation/portable-gateway-keys.test.mjs +4 -4
- package/dist/federation/portable-inbox.test.mjs +4 -4
- package/dist/federation/portable.test.mjs +4 -4
- package/dist/federation/send.test.mjs +84 -3
- package/dist/federation/tasks/enqueue.test.mjs +3 -3
- package/dist/federation/tasks/tasks.test.mjs +4 -4
- package/dist/federation/temporal.test.mjs +1 -1
- package/dist/federation/webfinger.test.mjs +2 -2
- package/dist/{federation-D4j-ZXUN.cjs → federation-2e8XrN7q.cjs} +700 -70
- package/dist/{http-C_jobbLz.cjs → http-BObQoB0j.cjs} +18 -3
- package/dist/{http-BoH-e8sR.mjs → http-CGcLv8rh.mjs} +9 -5
- package/dist/{http-CBRurnlR.js → http-Dgtt45-K.js} +13 -4
- package/dist/{key-BtO7WCfP.mjs → key-CPM4r6tP.mjs} +2 -2
- package/dist/{kv-cache-E0es0w4R.js → kv-cache-BweyIZTb.js} +35 -9
- package/dist/{kv-cache-BxUqVENn.cjs → kv-cache-D2fnB8qu.cjs} +34 -8
- package/dist/{kv-cache-CMuTXrkC.mjs → kv-cache-bdPqTR1k.mjs} +1 -1
- package/dist/{ld-YYBfDbe_.mjs → ld-lU_ZqzsQ.mjs} +4 -4
- package/dist/{metrics-DMqdDud_.mjs → metrics-B41pRPfd.mjs} +1 -1
- package/dist/{middleware-D6uiL1Tw.js → middleware-BYWyYjmX.js} +702 -72
- package/dist/{middleware-C97UJpkA.mjs → middleware-TrCVULgs.mjs} +1 -1
- package/dist/{middleware-CBGR92fz.mjs → middleware-xdukzV3x.mjs} +432 -81
- package/dist/{mod-C06GoL5Z.d.ts → mod-CnGtADvO.d.ts} +1 -1
- package/dist/{mod-CZetqT5h.d.ts → mod-DaiBrWDW.d.ts} +2 -0
- package/dist/{mod-B7SfEqIP.d.cts → mod-Decmur1U.d.cts} +1 -1
- package/dist/{mod-D4g9ffEn.d.cts → mod-Mt4aO3DX.d.cts} +2 -0
- package/dist/mod.cjs +5 -5
- package/dist/mod.d.cts +5 -5
- package/dist/mod.d.ts +5 -5
- package/dist/mod.js +5 -5
- package/dist/nodeinfo/client.test.mjs +49 -1
- package/dist/nodeinfo/handler.test.mjs +2 -2
- package/dist/nodeinfo/mod.cjs +1 -1
- package/dist/nodeinfo/mod.js +1 -1
- package/dist/{owner-Cn6CFYBy.mjs → owner-D63yPo_i.mjs} +3 -3
- package/dist/{portable-collection-CWgXwOdR.cjs → portable-collection-B8ojoQ50.cjs} +1 -1
- package/dist/{portable-collection-CyhFWEB-.js → portable-collection-C3xEzYri.js} +1 -1
- package/dist/{portable-collection-B3swTTja.mjs → portable-collection-_lZg-osX.mjs} +2 -2
- package/dist/{portable-key-HM5Siym0.mjs → portable-key-D8uWZF7b.mjs} +1 -1
- package/dist/{proof-DWkXfZIU.mjs → proof-C2YwD8a_.mjs} +10 -5
- package/dist/{send-BQDT_8bc.mjs → send-D9SGhxTB.mjs} +18 -4
- package/dist/sig/compound-proof-discovery.test.mjs +1 -1
- package/dist/sig/compound-proof-producer.test.mjs +1 -1
- package/dist/sig/compound-proof-verification.test.mjs +2 -2
- package/dist/sig/compound-proof.test.mjs +2 -2
- package/dist/sig/http.test.mjs +42 -2
- package/dist/sig/key.test.mjs +1 -1
- package/dist/sig/ld.test.mjs +5 -2
- package/dist/sig/mod.cjs +2 -2
- package/dist/sig/mod.js +2 -2
- package/dist/sig/owner.test.mjs +1 -1
- package/dist/sig/portable-collection.test.mjs +2 -2
- package/dist/sig/portable-key.test.mjs +6 -6
- package/dist/sig/proof.test.mjs +3 -3
- package/dist/{tasks-dbCShlCb.mjs → tasks-IG3pHfvZ.mjs} +1 -1
- package/dist/{temporal-CFI__wjQ.mjs → temporal-q6FSekcN.mjs} +1 -1
- package/dist/testing/context.test.mjs +1 -1
- package/dist/testing/kv.test.mjs +3 -3
- package/dist/testing/mod.d.mts +393 -4
- package/dist/testing/mod.mjs +4 -4
- package/dist/{testing-otQhu4aD.mjs → testing-C1itIjqW.mjs} +3 -3
- package/dist/{types-C_cNlNVK.js → types-_S2_MeLF.js} +7 -3
- package/dist/{types-Be2zbyeG.cjs → types-poIao40f.cjs} +7 -3
- package/dist/utils/docloader.test.mjs +297 -2
- package/dist/utils/kv-cache.test.mjs +1 -1
- package/dist/utils/mod.cjs +1 -1
- package/dist/utils/mod.d.cts +1 -1
- package/dist/utils/mod.d.ts +1 -1
- package/dist/utils/mod.js +1 -1
- package/dist/vocab/cjs.test.mjs +1 -1
- package/package.json +7 -7
- package/dist/docloader-C4h6PL7m.mjs +0 -53
|
@@ -1,34 +1,34 @@
|
|
|
1
1
|
import { Temporal } from "temporal-polyfill";
|
|
2
2
|
import "urlpattern-polyfill";
|
|
3
3
|
globalThis.addEventListener = () => {};
|
|
4
|
-
import { n as version, t as name } from "./deno-
|
|
5
|
-
import { a as instrumentDocumentLoader, b as recordWebFingerHandle, c as recordCircuitBreakerStateChange, d as recordCollectionRequest, f as recordCollectionTotalItems, g as recordInboxActivity, h as recordFanoutRecipients, i as getRemoteHost, l as recordCollectionDispatchDuration, n as getDurationMs, o as isAbortError, r as getFederationMetrics, u as recordCollectionPageItems, v as recordOutboxActivity, x as registerQueueDepthGauge, y as recordOutboxEnqueue } from "./metrics-
|
|
4
|
+
import { n as version, t as name } from "./deno-D1gcWWqB.mjs";
|
|
5
|
+
import { a as instrumentDocumentLoader, b as recordWebFingerHandle, c as recordCircuitBreakerStateChange, d as recordCollectionRequest, f as recordCollectionTotalItems, g as recordInboxActivity, h as recordFanoutRecipients, i as getRemoteHost, l as recordCollectionDispatchDuration, n as getDurationMs, o as isAbortError, r as getFederationMetrics, u as recordCollectionPageItems, v as recordOutboxActivity, x as registerQueueDepthGauge, y as recordOutboxEnqueue } from "./metrics-B41pRPfd.mjs";
|
|
6
6
|
import { t as formatAcceptSignature } from "./accept-CPVqki78.mjs";
|
|
7
7
|
import { a as hasPortableActor, c as isPortableId, l as isPortableKeyId, n as getGatewayKeyBase, o as isCompatibleKeyId, r as getPortableDid, t as getCanonicalPortableId, u as isPortableUri } from "./portable-key-id-Bg259c4Z.mjs";
|
|
8
|
-
import { c as validateCryptoKey, n as exportJwk, s as importJwk } from "./key-
|
|
8
|
+
import { c as validateCryptoKey, n as exportJwk, s as importJwk } from "./key-CPM4r6tP.mjs";
|
|
9
9
|
import { r as normalizeOutgoingActivityJsonLd } from "./outgoing-jsonld-4IJqPYos.mjs";
|
|
10
|
-
import { _ as wrapContextLoaderForJsonLd, a as compactJsonLd, c as getNormalizationContextLoader, d as isClearlyMalformedContextReference, f as isInvalidUrlTypeError, l as hasSignature, m as verifyCompactJsonLd, p as signJsonLd, r as assertSafeJsonLd, s as detachSignature, t as InvalidContextReferenceError, u as hasSignatureLike } from "./ld-
|
|
11
|
-
import {
|
|
12
|
-
import { a as listRequestSignatures, c as selectRequestSignature, d as verifyRequest, f as verifyRequestDetailed, s as parseRfc9421SignatureInput } from "./http-
|
|
13
|
-
import { t as getAuthenticatedDocumentLoader } from "./docloader-
|
|
14
|
-
import { n as kvCache } from "./kv-cache-
|
|
15
|
-
import { d as readBoundedText, l as BodyTooLargeError, t as getNodeInfo, u as MAX_BODY_SIZE } from "./client-
|
|
10
|
+
import { _ as wrapContextLoaderForJsonLd, a as compactJsonLd, c as getNormalizationContextLoader, d as isClearlyMalformedContextReference, f as isInvalidUrlTypeError, l as hasSignature, m as verifyCompactJsonLd, p as signJsonLd, r as assertSafeJsonLd, s as detachSignature, t as InvalidContextReferenceError, u as hasSignatureLike } from "./ld-lU_ZqzsQ.mjs";
|
|
11
|
+
import { a as signObject, c as verifyPortableObjectProof, i as hasProofLike, s as verifyObject, t as InvalidPortableObjectIdError, u as verifyPortableObjectProofWithRoot } from "./proof-C2YwD8a_.mjs";
|
|
12
|
+
import { a as listRequestSignatures, c as selectRequestSignature, d as verifyRequest, f as verifyRequestDetailed, s as parseRfc9421SignatureInput } from "./http-CGcLv8rh.mjs";
|
|
13
|
+
import { t as getAuthenticatedDocumentLoader } from "./docloader-BMqr3hji.mjs";
|
|
14
|
+
import { n as kvCache } from "./kv-cache-bdPqTR1k.mjs";
|
|
15
|
+
import { d as readBoundedText, l as BodyTooLargeError, t as getNodeInfo, u as MAX_BODY_SIZE } from "./client-CEnNz8O7.mjs";
|
|
16
16
|
import { t as nodeInfoToJson } from "./types-DE_LV1JB.mjs";
|
|
17
|
-
import { n as getKeyOwner, t as doesActorOwnKey } from "./owner-
|
|
18
|
-
import { t as verifyPortableObject } from "./portable-collection-
|
|
19
|
-
import { n as extractInboxes, r as sendActivity, t as SendActivityError } from "./send-
|
|
20
|
-
import { n as FederationBuilderImpl, t as ACTOR_ALIAS_PREFIX } from "./builder-
|
|
17
|
+
import { n as getKeyOwner, t as doesActorOwnKey } from "./owner-D63yPo_i.mjs";
|
|
18
|
+
import { t as verifyPortableObject } from "./portable-collection-_lZg-osX.mjs";
|
|
19
|
+
import { n as extractInboxes, r as sendActivity, t as SendActivityError } from "./send-D9SGhxTB.mjs";
|
|
20
|
+
import { n as FederationBuilderImpl, t as ACTOR_ALIAS_PREFIX } from "./builder-CLaS4-yA.mjs";
|
|
21
21
|
import { t as CircuitBreaker } from "./circuit-breaker-C5NqoLBJ.mjs";
|
|
22
22
|
import { t as buildCollectionSynchronizationHeader } from "./collection-CnnP1UAv.mjs";
|
|
23
|
-
import { a as verifyCompoundPortableObjectProofs, i as inspectCompoundPortableObjectApplicability, r as findUnsupportedCompoundProofShape, t as containsCompoundPortableObject } from "./compound-proof-
|
|
23
|
+
import { a as verifyCompoundPortableObjectProofs, i as inspectCompoundPortableObjectApplicability, r as findUnsupportedCompoundProofShape, s as verifyServedPortableObjects, t as containsCompoundPortableObject } from "./compound-proof-D7K94KPz.mjs";
|
|
24
24
|
import { t as KvKeyCache } from "./keycache-BInaECGj.mjs";
|
|
25
25
|
import { t as acceptsJsonLd } from "./negotiation-CCocWT_N.mjs";
|
|
26
|
-
import { t as hasMalformedKnownTemporalLiteral } from "./temporal-
|
|
26
|
+
import { t as hasMalformedKnownTemporalLiteral } from "./temporal-q6FSekcN.mjs";
|
|
27
27
|
import { t as createExponentialBackoffPolicy } from "./retry-zX5xw5e0.mjs";
|
|
28
28
|
import { t as TaskCodec } from "./codec-C6rTrpIp.mjs";
|
|
29
29
|
import { getLogger, withContext } from "@logtape/logtape";
|
|
30
30
|
import { RouterError } from "@fedify/uri-template";
|
|
31
|
-
import { Activity, Collection, CollectionPage, CryptographicKey, Link, Multikey, Object as Object$1, OrderedCollection, OrderedCollectionPage, Tombstone, getTypeId, lookupObject, traverseCollection } from "@fedify/vocab";
|
|
31
|
+
import { Activity, Collection, CollectionPage, CryptographicKey, Link, Multikey, Object as Object$1, OrderedCollection, OrderedCollectionPage, Tombstone, getTypeId, isActor, lookupObject, traverseCollection } from "@fedify/vocab";
|
|
32
32
|
import { lookupWebFinger } from "@fedify/webfinger";
|
|
33
33
|
import { SpanKind, SpanStatusCode, context, metrics, propagation, trace } from "@opentelemetry/api";
|
|
34
34
|
import { uniq } from "es-toolkit";
|
|
@@ -1275,6 +1275,46 @@ function portableObjectInternalServerError(request) {
|
|
|
1275
1275
|
headers: { "Content-Type": "text/plain; charset=utf-8" }
|
|
1276
1276
|
});
|
|
1277
1277
|
}
|
|
1278
|
+
/**
|
|
1279
|
+
* Checks the portable objects embedded in a portable collection or its page
|
|
1280
|
+
* that is about to be served against the FEP-ef61 proof policy. See
|
|
1281
|
+
* `verifyServedPortableObjects()`.
|
|
1282
|
+
* @returns `true` if the collection can be served.
|
|
1283
|
+
*/
|
|
1284
|
+
async function verifyPortableCollection(jsonLd, context, portable) {
|
|
1285
|
+
const logger = getLogger([
|
|
1286
|
+
"fedify",
|
|
1287
|
+
"federation",
|
|
1288
|
+
"collection"
|
|
1289
|
+
]);
|
|
1290
|
+
try {
|
|
1291
|
+
const result = await verifyServedPortableObjects(jsonLd, {
|
|
1292
|
+
contextLoader: context.contextLoader,
|
|
1293
|
+
documentLoader: context.documentLoader,
|
|
1294
|
+
tracerProvider: context.tracerProvider,
|
|
1295
|
+
meterProvider: context.meterProvider
|
|
1296
|
+
});
|
|
1297
|
+
if (result.verified) return true;
|
|
1298
|
+
logger.error("Refusing to serve the portable collection {collectionId}, as the portable object {objectId} it embeds at {path} does not satisfy the FEP-ef61 proof policy: {reason}. Embedded portable actors, activities, and objects need an Object Integrity Proof made with a key of the DID in their ID, and their own @context.", {
|
|
1299
|
+
collectionId: portable.id.href,
|
|
1300
|
+
objectId: result.id,
|
|
1301
|
+
path: result.path,
|
|
1302
|
+
reason: result.reason
|
|
1303
|
+
});
|
|
1304
|
+
} catch (error) {
|
|
1305
|
+
logger.error("Failed to verify the portable objects embedded in the portable collection {collectionId}:\n{error}", {
|
|
1306
|
+
collectionId: portable.id.href,
|
|
1307
|
+
error
|
|
1308
|
+
});
|
|
1309
|
+
}
|
|
1310
|
+
return false;
|
|
1311
|
+
}
|
|
1312
|
+
function respondWithPortableCollection(request, jsonLd) {
|
|
1313
|
+
return new Response(request.method === "HEAD" ? null : JSON.stringify(jsonLd), { headers: {
|
|
1314
|
+
"Content-Type": PORTABLE_OBJECT_CONTENT_TYPE,
|
|
1315
|
+
Vary: "Accept"
|
|
1316
|
+
} });
|
|
1317
|
+
}
|
|
1278
1318
|
const BUILT_IN_COLLECTION_METRIC_KINDS = /* @__PURE__ */ new Set([
|
|
1279
1319
|
"inbox",
|
|
1280
1320
|
"outbox",
|
|
@@ -1312,13 +1352,13 @@ function recordCollectionMetrics(meterProvider, base, result, options = {}) {
|
|
|
1312
1352
|
* @param parameters The parameters for handling the collection.
|
|
1313
1353
|
* @returns A promise that resolves to an HTTP response.
|
|
1314
1354
|
*/
|
|
1315
|
-
async function handleCollection(request, { name: name$
|
|
1316
|
-
const spanName = name$
|
|
1355
|
+
async function handleCollection(request, { name: name$1, identifier, uriGetter, filter, filterPredicate, context, collectionCallbacks, portable, tracerProvider, meterProvider, onUnauthorized, onNotFound }) {
|
|
1356
|
+
const spanName = name$1.trim().replace(/\s+/g, "_");
|
|
1317
1357
|
tracerProvider = tracerProvider ?? trace.getTracerProvider();
|
|
1318
1358
|
const tracer = tracerProvider.getTracer(name, version);
|
|
1319
1359
|
const cursor = new URL(request.url).searchParams.get("cursor");
|
|
1320
1360
|
const metricBase = {
|
|
1321
|
-
kind: getCollectionMetricKind(name$
|
|
1361
|
+
kind: getCollectionMetricKind(name$1),
|
|
1322
1362
|
page: cursor != null,
|
|
1323
1363
|
dispatcher: "built_in"
|
|
1324
1364
|
};
|
|
@@ -1336,8 +1376,13 @@ async function handleCollection(request, { name: name$2, identifier, uriGetter,
|
|
|
1336
1376
|
};
|
|
1337
1377
|
try {
|
|
1338
1378
|
if (collectionCallbacks == null) return finish(await onNotFound(request), "not_found");
|
|
1379
|
+
if (portable != null && collectionCallbacks.authorizePredicate != null) {
|
|
1380
|
+
if (!await collectionCallbacks.authorizePredicate(context, identifier)) return finish(await onUnauthorized(request), "unauthorized");
|
|
1381
|
+
}
|
|
1339
1382
|
let collection;
|
|
1340
|
-
const baseUri = uriGetter(identifier);
|
|
1383
|
+
const baseUri = portable?.id ?? uriGetter(identifier);
|
|
1384
|
+
const pageBase = portable?.view ?? context.url;
|
|
1385
|
+
const attribution = portable?.attribution ?? null;
|
|
1341
1386
|
if (cursor == null) {
|
|
1342
1387
|
const firstCursor = await collectionCallbacks.firstCursor?.(context, identifier);
|
|
1343
1388
|
const totalItems = filter == null ? await collectionCallbacks.counter?.(context, identifier) : void 0;
|
|
@@ -1359,7 +1404,7 @@ async function handleCollection(request, { name: name$2, identifier, uriGetter,
|
|
|
1359
1404
|
span.setStatus({ code: SpanStatusCode.ERROR });
|
|
1360
1405
|
return await onNotFound(request);
|
|
1361
1406
|
}
|
|
1362
|
-
const items = filterCollectionItems(page.items, name$
|
|
1407
|
+
const items = filterCollectionItems(page.items, name$1, filterPredicate);
|
|
1363
1408
|
itemCount = items.length;
|
|
1364
1409
|
span.setAttribute("fedify.collection.items", itemCount);
|
|
1365
1410
|
return items;
|
|
@@ -1377,29 +1422,31 @@ async function handleCollection(request, { name: name$2, identifier, uriGetter,
|
|
|
1377
1422
|
if (itemsOrResponse instanceof Response) return finish(itemsOrResponse, "not_found");
|
|
1378
1423
|
collection = new OrderedCollection({
|
|
1379
1424
|
id: baseUri,
|
|
1425
|
+
attribution,
|
|
1380
1426
|
totalItems: totalItemCount ?? null,
|
|
1381
1427
|
items: itemsOrResponse
|
|
1382
1428
|
});
|
|
1383
1429
|
} else {
|
|
1384
1430
|
const lastCursor = await collectionCallbacks.lastCursor?.(context, identifier);
|
|
1385
|
-
const first = new URL(
|
|
1431
|
+
const first = new URL(pageBase);
|
|
1386
1432
|
first.searchParams.set("cursor", firstCursor);
|
|
1387
1433
|
let last = null;
|
|
1388
1434
|
if (lastCursor != null) {
|
|
1389
|
-
last = new URL(
|
|
1435
|
+
last = new URL(pageBase);
|
|
1390
1436
|
last.searchParams.set("cursor", lastCursor);
|
|
1391
1437
|
}
|
|
1392
1438
|
collection = new OrderedCollection({
|
|
1393
1439
|
id: baseUri,
|
|
1440
|
+
attribution,
|
|
1394
1441
|
totalItems: totalItemCount ?? null,
|
|
1395
1442
|
first,
|
|
1396
1443
|
last
|
|
1397
1444
|
});
|
|
1398
1445
|
}
|
|
1399
1446
|
} else {
|
|
1400
|
-
const uri = new URL(baseUri);
|
|
1447
|
+
const uri = new URL(portable == null ? baseUri : pageBase);
|
|
1401
1448
|
uri.searchParams.set("cursor", cursor);
|
|
1402
|
-
const pageOrResponse = await tracer.startActiveSpan(`activitypub.dispatch_collection_page ${name$
|
|
1449
|
+
const pageOrResponse = await tracer.startActiveSpan(`activitypub.dispatch_collection_page ${name$1}`, {
|
|
1403
1450
|
kind: SpanKind.SERVER,
|
|
1404
1451
|
attributes: {
|
|
1405
1452
|
"activitypub.collection.id": uri.href,
|
|
@@ -1415,7 +1462,7 @@ async function handleCollection(request, { name: name$2, identifier, uriGetter,
|
|
|
1415
1462
|
span.setStatus({ code: SpanStatusCode.ERROR });
|
|
1416
1463
|
return await onNotFound(request);
|
|
1417
1464
|
}
|
|
1418
|
-
const items = filterCollectionItems(page.items, name$
|
|
1465
|
+
const items = filterCollectionItems(page.items, name$1, filterPredicate);
|
|
1419
1466
|
itemCount = items.length;
|
|
1420
1467
|
span.setAttribute("fedify.collection.items", itemCount);
|
|
1421
1468
|
return {
|
|
@@ -1437,28 +1484,30 @@ async function handleCollection(request, { name: name$2, identifier, uriGetter,
|
|
|
1437
1484
|
const { items, prevCursor, nextCursor } = pageOrResponse;
|
|
1438
1485
|
let prev = null;
|
|
1439
1486
|
if (prevCursor != null) {
|
|
1440
|
-
prev = new URL(
|
|
1487
|
+
prev = new URL(pageBase);
|
|
1441
1488
|
prev.searchParams.set("cursor", prevCursor);
|
|
1442
1489
|
}
|
|
1443
1490
|
let next = null;
|
|
1444
1491
|
if (nextCursor != null) {
|
|
1445
|
-
next = new URL(
|
|
1492
|
+
next = new URL(pageBase);
|
|
1446
1493
|
next.searchParams.set("cursor", nextCursor);
|
|
1447
1494
|
}
|
|
1448
|
-
const partOf = new URL(
|
|
1495
|
+
const partOf = new URL(pageBase);
|
|
1449
1496
|
partOf.searchParams.delete("cursor");
|
|
1450
1497
|
collection = new OrderedCollectionPage({
|
|
1451
1498
|
id: uri,
|
|
1499
|
+
attribution,
|
|
1452
1500
|
prev,
|
|
1453
1501
|
next,
|
|
1454
1502
|
items,
|
|
1455
1503
|
partOf
|
|
1456
1504
|
});
|
|
1457
1505
|
}
|
|
1458
|
-
if (collectionCallbacks.authorizePredicate != null) {
|
|
1506
|
+
if (portable == null && collectionCallbacks.authorizePredicate != null) {
|
|
1459
1507
|
if (!await collectionCallbacks.authorizePredicate(context, identifier)) return finish(await onUnauthorized(request), "unauthorized");
|
|
1460
1508
|
}
|
|
1461
1509
|
const jsonLd = await collection.toJsonLd(context);
|
|
1510
|
+
if (portable != null) return await verifyPortableCollection(jsonLd, context, portable) ? finish(respondWithPortableCollection(request, jsonLd), "served") : finish(portableObjectInternalServerError(request), "error");
|
|
1462
1511
|
return finish(new Response(JSON.stringify(jsonLd), { headers: {
|
|
1463
1512
|
"Content-Type": "application/activity+json",
|
|
1464
1513
|
Vary: "Accept"
|
|
@@ -2506,19 +2555,11 @@ function recordDeferredPendingCollectionMetrics(error, result, response) {
|
|
|
2506
2555
|
pendingCollectionMetricRecorders.delete(error);
|
|
2507
2556
|
recorder?.(result, response);
|
|
2508
2557
|
}
|
|
2509
|
-
async function _handleCustomCollection(request, { name, values, context, tracerProvider, meterProvider, collectionCallbacks: callbacks, filterPredicate }) {
|
|
2558
|
+
async function _handleCustomCollection(request, { name, values, context, tracerProvider, meterProvider, collectionCallbacks: callbacks, filterPredicate, portable }) {
|
|
2510
2559
|
verifyDefined(callbacks);
|
|
2511
2560
|
await authIfNeeded(context, values, callbacks);
|
|
2512
2561
|
const cursor = new URL(request.url).searchParams.get("cursor");
|
|
2513
|
-
|
|
2514
|
-
try {
|
|
2515
|
-
const response = await handler.toJsonLd().then(respondAsActivity);
|
|
2516
|
-
handler.recordPendingCollectionMetrics("served", response);
|
|
2517
|
-
return response;
|
|
2518
|
-
} catch (e) {
|
|
2519
|
-
if (!deferPendingCollectionMetrics(e, (result, response) => handler.recordPendingCollectionMetrics(result, response))) handler.recordPendingCollectionMetrics("error");
|
|
2520
|
-
throw e;
|
|
2521
|
-
}
|
|
2562
|
+
return await respondWithCustomCollection(request, new CustomCollectionHandler(name, values, context, callbacks, tracerProvider, meterProvider, Collection, CollectionPage, filterPredicate, portable).fetchCollection(cursor), context, portable);
|
|
2522
2563
|
}
|
|
2523
2564
|
/**
|
|
2524
2565
|
* Handles an ordered collection request.
|
|
@@ -2532,13 +2573,19 @@ async function _handleCustomCollection(request, { name, values, context, tracerP
|
|
|
2532
2573
|
* @since 1.8.0
|
|
2533
2574
|
*/
|
|
2534
2575
|
const handleOrderedCollection = exceptWrapper(_handleOrderedCollection);
|
|
2535
|
-
async function _handleOrderedCollection(request, { name, values, context, tracerProvider, meterProvider, collectionCallbacks: callbacks, filterPredicate }) {
|
|
2576
|
+
async function _handleOrderedCollection(request, { name, values, context, tracerProvider, meterProvider, collectionCallbacks: callbacks, filterPredicate, portable }) {
|
|
2536
2577
|
verifyDefined(callbacks);
|
|
2537
2578
|
await authIfNeeded(context, values, callbacks);
|
|
2538
2579
|
const cursor = new URL(request.url).searchParams.get("cursor");
|
|
2539
|
-
|
|
2580
|
+
return await respondWithCustomCollection(request, new CustomCollectionHandler(name, values, context, callbacks, tracerProvider, meterProvider, OrderedCollection, OrderedCollectionPage, filterPredicate, portable).fetchCollection(cursor), context, portable);
|
|
2581
|
+
}
|
|
2582
|
+
async function respondWithCustomCollection(request, handler, context, portable) {
|
|
2540
2583
|
try {
|
|
2541
|
-
const
|
|
2584
|
+
const jsonLd = await handler.toJsonLd();
|
|
2585
|
+
let response;
|
|
2586
|
+
if (portable == null) response = respondAsActivity(jsonLd);
|
|
2587
|
+
else if (await verifyPortableCollection(jsonLd, context, portable)) response = respondWithPortableCollection(request, jsonLd);
|
|
2588
|
+
else throw new PortableCollectionRefusedError();
|
|
2542
2589
|
handler.recordPendingCollectionMetrics("served", response);
|
|
2543
2590
|
return response;
|
|
2544
2591
|
} catch (e) {
|
|
@@ -2568,6 +2615,7 @@ var CustomCollectionHandler = class {
|
|
|
2568
2615
|
Collection;
|
|
2569
2616
|
CollectionPage;
|
|
2570
2617
|
filterPredicate;
|
|
2618
|
+
portable;
|
|
2571
2619
|
/**
|
|
2572
2620
|
* The tracer for telemetry.
|
|
2573
2621
|
* @type {Tracer}
|
|
@@ -2604,8 +2652,8 @@ var CustomCollectionHandler = class {
|
|
|
2604
2652
|
* @param CollectionPage The CollectionPage constructor.
|
|
2605
2653
|
* @param filterPredicate Optional filter predicate for items.
|
|
2606
2654
|
*/
|
|
2607
|
-
constructor(name$
|
|
2608
|
-
this.name = name$
|
|
2655
|
+
constructor(name$2, values, context, callbacks, tracerProvider = trace.getTracerProvider(), meterProvider, Collection, CollectionPage, filterPredicate, portable) {
|
|
2656
|
+
this.name = name$2;
|
|
2609
2657
|
this.values = values;
|
|
2610
2658
|
this.context = context;
|
|
2611
2659
|
this.callbacks = callbacks;
|
|
@@ -2614,9 +2662,14 @@ var CustomCollectionHandler = class {
|
|
|
2614
2662
|
this.Collection = Collection;
|
|
2615
2663
|
this.CollectionPage = CollectionPage;
|
|
2616
2664
|
this.filterPredicate = filterPredicate;
|
|
2665
|
+
this.portable = portable;
|
|
2617
2666
|
this.name = this.name.trim().replace(/\s+/g, "_");
|
|
2618
2667
|
this.#tracer = this.tracerProvider.getTracer(name, version);
|
|
2619
|
-
this.#id = new URL(this.context.url);
|
|
2668
|
+
if (portable == null) this.#id = new URL(this.context.url);
|
|
2669
|
+
else {
|
|
2670
|
+
const cursor = this.context.url.searchParams.get("cursor");
|
|
2671
|
+
this.#id = cursor == null ? new URL(portable.view) : appendCursorIfExists(portable.view, cursor);
|
|
2672
|
+
}
|
|
2620
2673
|
this.#dispatcher = callbacks.dispatcher.bind(callbacks);
|
|
2621
2674
|
}
|
|
2622
2675
|
/**
|
|
@@ -2667,6 +2720,7 @@ var CustomCollectionHandler = class {
|
|
|
2667
2720
|
this.recordPendingCollectionItemCount(true, items.length);
|
|
2668
2721
|
return {
|
|
2669
2722
|
id,
|
|
2723
|
+
attribution: this.portable?.attribution ?? null,
|
|
2670
2724
|
partOf,
|
|
2671
2725
|
items,
|
|
2672
2726
|
prev: this.appendToUrl(prevCursor),
|
|
@@ -2688,6 +2742,7 @@ var CustomCollectionHandler = class {
|
|
|
2688
2742
|
});
|
|
2689
2743
|
return {
|
|
2690
2744
|
id: this.#id,
|
|
2745
|
+
attribution: this.portable?.attribution ?? null,
|
|
2691
2746
|
first: this.appendToUrl(firstCursor),
|
|
2692
2747
|
last: this.appendToUrl(lastCursor),
|
|
2693
2748
|
totalItems
|
|
@@ -2704,6 +2759,7 @@ var CustomCollectionHandler = class {
|
|
|
2704
2759
|
this.recordPendingCollectionItemCount(false, items.length);
|
|
2705
2760
|
return {
|
|
2706
2761
|
id: this.#id,
|
|
2762
|
+
attribution: this.portable?.attribution ?? null,
|
|
2707
2763
|
totalItems,
|
|
2708
2764
|
items
|
|
2709
2765
|
};
|
|
@@ -2821,7 +2877,7 @@ var CustomCollectionHandler = class {
|
|
|
2821
2877
|
* @returns The URL with cursor appended, or null if cursor is null/undefined.
|
|
2822
2878
|
*/
|
|
2823
2879
|
appendToUrl(cursor) {
|
|
2824
|
-
return appendCursorIfExists(this.context.url, cursor);
|
|
2880
|
+
return appendCursorIfExists(this.portable?.view ?? this.context.url, cursor);
|
|
2825
2881
|
}
|
|
2826
2882
|
/**
|
|
2827
2883
|
* Gets the stored collection or collection page.
|
|
@@ -2898,6 +2954,12 @@ function exceptWrapper(handler) {
|
|
|
2898
2954
|
recordCollectionRequest(meterProvider, collectionAttributes(metricBase, "not_found", response));
|
|
2899
2955
|
return response;
|
|
2900
2956
|
}
|
|
2957
|
+
case PortableCollectionRefusedError: {
|
|
2958
|
+
const response = portableObjectInternalServerError(request);
|
|
2959
|
+
recordDeferredPendingCollectionMetrics(error, "error", response);
|
|
2960
|
+
recordCollectionRequest(meterProvider, collectionAttributes(metricBase, "error", response));
|
|
2961
|
+
return response;
|
|
2962
|
+
}
|
|
2901
2963
|
case UnauthorizedError: {
|
|
2902
2964
|
const response = await onUnauthorized(request);
|
|
2903
2965
|
recordDeferredPendingCollectionMetrics(error, "unauthorized", response);
|
|
@@ -2994,6 +3056,16 @@ var UnauthorizedError = class extends HandlerError {
|
|
|
2994
3056
|
}
|
|
2995
3057
|
};
|
|
2996
3058
|
/**
|
|
3059
|
+
* Error thrown when a portable collection is not served because it embeds
|
|
3060
|
+
* portable objects that do not satisfy the FEP-ef61 proof policy.
|
|
3061
|
+
* @since 2.4.0
|
|
3062
|
+
*/
|
|
3063
|
+
var PortableCollectionRefusedError = class extends HandlerError {
|
|
3064
|
+
constructor() {
|
|
3065
|
+
super("The portable collection embeds unverifiable portable objects.");
|
|
3066
|
+
}
|
|
3067
|
+
};
|
|
3068
|
+
/**
|
|
2997
3069
|
* Responds with the given object in JSON-LD format.
|
|
2998
3070
|
*
|
|
2999
3071
|
* @param object The object to respond with.
|
|
@@ -3133,6 +3205,100 @@ async function verifyPortableActivityId(activityId, jsonLd, options) {
|
|
|
3133
3205
|
return null;
|
|
3134
3206
|
}
|
|
3135
3207
|
//#endregion
|
|
3208
|
+
//#region src/federation/portable-collection.ts
|
|
3209
|
+
/**
|
|
3210
|
+
* The query parameters of a gateway request for a portable collection that
|
|
3211
|
+
* do not select a view of the collection: the page cursor, and FEP-ef61
|
|
3212
|
+
* location hints (`@gateway`, and the legacy `gateways`).
|
|
3213
|
+
*/
|
|
3214
|
+
const NON_VIEW_PARAMETERS = /* @__PURE__ */ new Set([
|
|
3215
|
+
"cursor",
|
|
3216
|
+
"@gateway",
|
|
3217
|
+
"gateways"
|
|
3218
|
+
]);
|
|
3219
|
+
/**
|
|
3220
|
+
* Checks that a collection requested through the FEP-ef61 gateway endpoint
|
|
3221
|
+
* belongs to a portable actor under the requested DID, and determines
|
|
3222
|
+
* the collection's ID from the actor.
|
|
3223
|
+
*
|
|
3224
|
+
* Collection dispatchers return items, not documents with IDs, so unlike
|
|
3225
|
+
* object dispatchers, there is nothing else to tell whether the requested
|
|
3226
|
+
* DID controls the collection.
|
|
3227
|
+
*
|
|
3228
|
+
* @param actor The actor that owns the collection according to
|
|
3229
|
+
* the application, dispatched without the portable request,
|
|
3230
|
+
* or `null` if there is none.
|
|
3231
|
+
* @param options The requested DID, and either the requested collection's
|
|
3232
|
+
* canonical ID and the actor's property that has to refer to
|
|
3233
|
+
* it (for the built-in collections of an actor), or
|
|
3234
|
+
* the requested collection's portable ID (for a custom
|
|
3235
|
+
* collection, whose ID has the same form as the actor's).
|
|
3236
|
+
* @returns The resolution.
|
|
3237
|
+
*/
|
|
3238
|
+
function resolvePortableCollectionOwner(actor, options) {
|
|
3239
|
+
if (!isActor(actor)) return {
|
|
3240
|
+
status: "rejected",
|
|
3241
|
+
reason: "notFound"
|
|
3242
|
+
};
|
|
3243
|
+
const ownerId = actor.id;
|
|
3244
|
+
const did = ownerId == null || !isPortableId(ownerId) ? null : getPortableDid(ownerId);
|
|
3245
|
+
if (ownerId == null || did == null) return {
|
|
3246
|
+
status: "rejected",
|
|
3247
|
+
reason: "notPortable"
|
|
3248
|
+
};
|
|
3249
|
+
if (did !== options.authority) return {
|
|
3250
|
+
status: "rejected",
|
|
3251
|
+
reason: "authorityMismatch"
|
|
3252
|
+
};
|
|
3253
|
+
if ("portableId" in options) return {
|
|
3254
|
+
status: "accepted",
|
|
3255
|
+
ownerId,
|
|
3256
|
+
collectionId: getCustomCollectionId(ownerId, actor, options.portableId)
|
|
3257
|
+
};
|
|
3258
|
+
const property = options.property(actor);
|
|
3259
|
+
if (property == null || getCanonicalPortableId(property) !== options.canonicalId) return {
|
|
3260
|
+
status: "rejected",
|
|
3261
|
+
reason: "collectionMismatch"
|
|
3262
|
+
};
|
|
3263
|
+
const collectionId = new URL(property);
|
|
3264
|
+
collectionId.search = "";
|
|
3265
|
+
collectionId.hash = "";
|
|
3266
|
+
return {
|
|
3267
|
+
status: "accepted",
|
|
3268
|
+
ownerId,
|
|
3269
|
+
collectionId
|
|
3270
|
+
};
|
|
3271
|
+
}
|
|
3272
|
+
/**
|
|
3273
|
+
* Gets the ID of a custom portable collection in the same form as its owner's
|
|
3274
|
+
* ID: a portable ID if the owner has one, or otherwise a compatible identifier
|
|
3275
|
+
* on the owner's first gateway, as FEP-ef61 requires of publishers.
|
|
3276
|
+
*/
|
|
3277
|
+
function getCustomCollectionId(ownerId, owner, portableId) {
|
|
3278
|
+
if (isPortableUri(ownerId)) return portableId;
|
|
3279
|
+
const gateway = owner.gateways[0];
|
|
3280
|
+
if (gateway == null || !isGatewayUrl(gateway)) return portableId;
|
|
3281
|
+
try {
|
|
3282
|
+
return toCompatibleEf61Id(portableId, gateway);
|
|
3283
|
+
} catch (error) {
|
|
3284
|
+
if (error instanceof TypeError) return portableId;
|
|
3285
|
+
throw error;
|
|
3286
|
+
}
|
|
3287
|
+
}
|
|
3288
|
+
/**
|
|
3289
|
+
* Builds the URI that the pages of a portable collection are relative to:
|
|
3290
|
+
* the collection's ID with the query parameters of the requested view, e.g.,
|
|
3291
|
+
* a filter, but without the page cursor and location hints.
|
|
3292
|
+
* @param collectionId The ID of the collection.
|
|
3293
|
+
* @param requestUrl The URL of the gateway request.
|
|
3294
|
+
* @returns The view URI.
|
|
3295
|
+
*/
|
|
3296
|
+
function buildPortableCollectionView(collectionId, requestUrl) {
|
|
3297
|
+
const view = new URL(collectionId);
|
|
3298
|
+
for (const [name, value] of requestUrl.searchParams) if (!NON_VIEW_PARAMETERS.has(name)) view.searchParams.append(name, value);
|
|
3299
|
+
return view;
|
|
3300
|
+
}
|
|
3301
|
+
//#endregion
|
|
3136
3302
|
//#region src/federation/outgoing-proof.ts
|
|
3137
3303
|
function formatKeyIds(candidates) {
|
|
3138
3304
|
return candidates.map((c) => c.verificationMethod.href).join(", ");
|
|
@@ -5415,12 +5581,7 @@ var FederationImpl = class extends FederationBuilderImpl {
|
|
|
5415
5581
|
onNotFound
|
|
5416
5582
|
});
|
|
5417
5583
|
case "followers": {
|
|
5418
|
-
|
|
5419
|
-
if (baseUrl != null) try {
|
|
5420
|
-
baseUrl = `${new URL(baseUrl).origin}/`;
|
|
5421
|
-
} catch {
|
|
5422
|
-
baseUrl = null;
|
|
5423
|
-
}
|
|
5584
|
+
const baseUrl = getFollowersBaseUrl(url);
|
|
5424
5585
|
return await handleCollection(request, {
|
|
5425
5586
|
name: "followers",
|
|
5426
5587
|
identifier: route.values.identifier,
|
|
@@ -5431,7 +5592,7 @@ var FederationImpl = class extends FederationBuilderImpl {
|
|
|
5431
5592
|
},
|
|
5432
5593
|
context,
|
|
5433
5594
|
filter: baseUrl != null ? new URL(baseUrl) : void 0,
|
|
5434
|
-
filterPredicate: baseUrl != null ? (
|
|
5595
|
+
filterPredicate: baseUrl != null ? getFollowersFilterPredicate(baseUrl) : void 0,
|
|
5435
5596
|
collectionCallbacks: this.followersCallbacks,
|
|
5436
5597
|
tracerProvider: this.tracerProvider,
|
|
5437
5598
|
meterProvider: this._meterProvider,
|
|
@@ -5525,17 +5686,30 @@ var FederationImpl = class extends FederationBuilderImpl {
|
|
|
5525
5686
|
}
|
|
5526
5687
|
const route = this.router.route(portable.path);
|
|
5527
5688
|
const isActor = route != null && (route.name === "actor" || route.name.startsWith("actorAlias:"));
|
|
5528
|
-
|
|
5689
|
+
const collectionRoute = route == null ? void 0 : getCollectionMetricRoute(route.name.replace(/:.*$/, ""));
|
|
5690
|
+
if (route == null || !isActor && !route.name.startsWith("object:") && collectionRoute == null) {
|
|
5529
5691
|
metricState.endpoint = "not_found";
|
|
5530
5692
|
return await onNotFound(request);
|
|
5531
5693
|
}
|
|
5532
5694
|
metricState.routeTemplate = PORTABLE_GATEWAY_ROUTE_PREFIX + route.template;
|
|
5533
|
-
metricState.endpoint =
|
|
5695
|
+
metricState.endpoint = getEndpointCategory(route.name);
|
|
5534
5696
|
span.updateName(`${request.method} ${metricState.routeTemplate}`);
|
|
5535
5697
|
if (!acceptsJsonLd(request)) {
|
|
5536
5698
|
metricState.endpoint = "not_acceptable";
|
|
5537
|
-
|
|
5699
|
+
const response = await onNotAcceptable(request);
|
|
5700
|
+
if (collectionRoute != null) recordCollectionRequest(this._meterProvider, {
|
|
5701
|
+
...collectionRoute,
|
|
5702
|
+
page: new URL(request.url).searchParams.get("cursor") != null,
|
|
5703
|
+
result: "not_acceptable",
|
|
5704
|
+
statusCode: response.status
|
|
5705
|
+
});
|
|
5706
|
+
return response;
|
|
5538
5707
|
}
|
|
5708
|
+
if (collectionRoute != null) return await this.#fetchPortableCollection(request, portable, route, {
|
|
5709
|
+
onNotFound,
|
|
5710
|
+
onUnauthorized,
|
|
5711
|
+
contextData
|
|
5712
|
+
});
|
|
5539
5713
|
if (isActor) {
|
|
5540
5714
|
const identifier = route.name.startsWith("actorAlias:") ? route.name.substring(ACTOR_ALIAS_PREFIX.length) : route.values.identifier;
|
|
5541
5715
|
const context = this.#createContext(request, contextData, {
|
|
@@ -5574,6 +5748,108 @@ var FederationImpl = class extends FederationBuilderImpl {
|
|
|
5574
5748
|
onNotFound
|
|
5575
5749
|
});
|
|
5576
5750
|
}
|
|
5751
|
+
async #fetchPortableCollection(request, portable, route, { onNotFound, onUnauthorized, contextData }) {
|
|
5752
|
+
const logger = getLogger([
|
|
5753
|
+
"fedify",
|
|
5754
|
+
"federation",
|
|
5755
|
+
"collection"
|
|
5756
|
+
]);
|
|
5757
|
+
const url = new URL(request.url);
|
|
5758
|
+
const { authority } = portable.portableRequest;
|
|
5759
|
+
const ownerContext = this.#createContext(request, contextData);
|
|
5760
|
+
const context = this.#createContext(request, contextData, { portableRequest: portable.portableRequest });
|
|
5761
|
+
const reject = async (reason, identifier) => {
|
|
5762
|
+
logger.debug("Not serving the portable collection {collection} of the actor {identifier}: {reason}.", {
|
|
5763
|
+
collection: portable.canonicalId,
|
|
5764
|
+
identifier,
|
|
5765
|
+
reason
|
|
5766
|
+
});
|
|
5767
|
+
return await onNotFound(request);
|
|
5768
|
+
};
|
|
5769
|
+
const routeName = route.name.replace(/:.*$/, "");
|
|
5770
|
+
if (routeName === "collection" || routeName === "orderedCollection") {
|
|
5771
|
+
const name = route.name.replace(/^(?:ordered)?[cC]ollection:/, "");
|
|
5772
|
+
const callbacks = this.collectionCallbacks[name];
|
|
5773
|
+
if (callbacks == null) return await onNotFound(request);
|
|
5774
|
+
const mapper = callbacks.portableOwnerMapper;
|
|
5775
|
+
if (mapper == null) return await reject("noPortableOwnerMapper");
|
|
5776
|
+
const identifier = await mapper(ownerContext, route.values);
|
|
5777
|
+
if (identifier == null) return await reject("notPortable");
|
|
5778
|
+
const resolution = resolvePortableCollectionOwner(await this.actorCallbacks?.dispatcher?.(ownerContext, identifier), {
|
|
5779
|
+
authority,
|
|
5780
|
+
portableId: portable.portableRequest.id
|
|
5781
|
+
});
|
|
5782
|
+
if (resolution.status === "rejected") return await reject(resolution.reason, identifier);
|
|
5783
|
+
const view = buildPortableCollectionView(resolution.collectionId, url);
|
|
5784
|
+
return await (routeName === "collection" ? handleCustomCollection : handleOrderedCollection)(request, {
|
|
5785
|
+
name,
|
|
5786
|
+
context,
|
|
5787
|
+
values: route.values,
|
|
5788
|
+
collectionCallbacks: callbacks,
|
|
5789
|
+
portable: {
|
|
5790
|
+
id: view,
|
|
5791
|
+
view,
|
|
5792
|
+
attribution: resolution.ownerId
|
|
5793
|
+
},
|
|
5794
|
+
tracerProvider: this.tracerProvider,
|
|
5795
|
+
meterProvider: this._meterProvider,
|
|
5796
|
+
onUnauthorized,
|
|
5797
|
+
onNotFound
|
|
5798
|
+
});
|
|
5799
|
+
}
|
|
5800
|
+
const identifier = route.values.identifier;
|
|
5801
|
+
const serve = async (name, callbacks, property, filter) => {
|
|
5802
|
+
if (callbacks == null) return await onNotFound(request);
|
|
5803
|
+
const resolution = resolvePortableCollectionOwner(await this.actorCallbacks?.dispatcher?.(ownerContext, identifier), {
|
|
5804
|
+
authority,
|
|
5805
|
+
canonicalId: portable.canonicalId,
|
|
5806
|
+
property
|
|
5807
|
+
});
|
|
5808
|
+
if (resolution.status === "rejected") return await reject(resolution.reason, identifier);
|
|
5809
|
+
const id = new URL(resolution.collectionId);
|
|
5810
|
+
if (filter != null) id.searchParams.set(...filter.parameter);
|
|
5811
|
+
const view = buildPortableCollectionView(resolution.collectionId, url);
|
|
5812
|
+
if (routeName === "followers") {
|
|
5813
|
+
view.searchParams.delete("base-url");
|
|
5814
|
+
if (filter != null) view.searchParams.set(...filter.parameter);
|
|
5815
|
+
}
|
|
5816
|
+
return await handleCollection(request, {
|
|
5817
|
+
name,
|
|
5818
|
+
identifier,
|
|
5819
|
+
uriGetter: () => id,
|
|
5820
|
+
context,
|
|
5821
|
+
filter: filter?.value,
|
|
5822
|
+
filterPredicate: filter?.predicate,
|
|
5823
|
+
collectionCallbacks: callbacks,
|
|
5824
|
+
portable: {
|
|
5825
|
+
id,
|
|
5826
|
+
view,
|
|
5827
|
+
attribution: resolution.ownerId
|
|
5828
|
+
},
|
|
5829
|
+
tracerProvider: this.tracerProvider,
|
|
5830
|
+
meterProvider: this._meterProvider,
|
|
5831
|
+
onUnauthorized,
|
|
5832
|
+
onNotFound
|
|
5833
|
+
});
|
|
5834
|
+
};
|
|
5835
|
+
switch (routeName) {
|
|
5836
|
+
case "outbox": return await serve("outbox", this.outboxCallbacks, (a) => a.outboxId);
|
|
5837
|
+
case "inbox": return await serve("inbox", this.inboxCallbacks, (a) => a.inboxId);
|
|
5838
|
+
case "following": return await serve("following", this.followingCallbacks, (a) => a.followingId);
|
|
5839
|
+
case "followers": {
|
|
5840
|
+
const baseUrl = getFollowersBaseUrl(url);
|
|
5841
|
+
return await serve("followers", this.followersCallbacks, (a) => a.followersId, baseUrl == null ? void 0 : {
|
|
5842
|
+
value: new URL(baseUrl),
|
|
5843
|
+
predicate: getFollowersFilterPredicate(baseUrl),
|
|
5844
|
+
parameter: ["base-url", baseUrl]
|
|
5845
|
+
});
|
|
5846
|
+
}
|
|
5847
|
+
case "liked": return await serve("liked", this.likedCallbacks, (a) => a.likedId);
|
|
5848
|
+
case "featured": return await serve("featured", this.featuredCallbacks, (a) => a.featuredId);
|
|
5849
|
+
case "featuredTags": return await serve("featured tags", this.featuredTagsCallbacks, (a) => a.featuredTagsId);
|
|
5850
|
+
}
|
|
5851
|
+
return await onNotFound(request);
|
|
5852
|
+
}
|
|
5577
5853
|
async #handleInbox(request, { recipient, context, contextData, onNotFound, portableInbox }) {
|
|
5578
5854
|
if (!this.manuallyStartQueue) this._startQueueInternal(contextData);
|
|
5579
5855
|
const inboxContextFactory = context.toInboxContext.bind(context);
|
|
@@ -5760,6 +6036,41 @@ var FederationImpl = class extends FederationBuilderImpl {
|
|
|
5760
6036
|
};
|
|
5761
6037
|
const PORTABLE_GATEWAY_ROUTE_PREFIX = "/.well-known/apgateway/{did}";
|
|
5762
6038
|
/**
|
|
6039
|
+
* Gets the origin that a followers collection request filters followers by,
|
|
6040
|
+
* i.e., its normalized `base-url` query parameter (FEP-8fcf).
|
|
6041
|
+
* @returns The origin with a trailing slash, or `null` if the parameter is
|
|
6042
|
+
* missing or invalid.
|
|
6043
|
+
*/
|
|
6044
|
+
function getFollowersBaseUrl(url) {
|
|
6045
|
+
const baseUrl = url.searchParams.get("base-url");
|
|
6046
|
+
if (baseUrl == null) return null;
|
|
6047
|
+
try {
|
|
6048
|
+
return `${new URL(baseUrl).origin}/`;
|
|
6049
|
+
} catch {
|
|
6050
|
+
return null;
|
|
6051
|
+
}
|
|
6052
|
+
}
|
|
6053
|
+
function getFollowersFilterPredicate(baseUrl) {
|
|
6054
|
+
return (i) => (i instanceof URL ? i.href : i.id?.href ?? "").startsWith(baseUrl);
|
|
6055
|
+
}
|
|
6056
|
+
const ACTOR_COLLECTION_ROUTE_ERRORS = {
|
|
6057
|
+
outbox: "No outbox dispatcher registered.",
|
|
6058
|
+
following: "No following collection path registered.",
|
|
6059
|
+
followers: "No followers collection path registered.",
|
|
6060
|
+
liked: "No liked collection path registered.",
|
|
6061
|
+
featured: "No featured collection path registered.",
|
|
6062
|
+
featuredTags: "No featured tags collection path registered."
|
|
6063
|
+
};
|
|
6064
|
+
/**
|
|
6065
|
+
* Checks that the authority of a portable ID to build is given, either
|
|
6066
|
+
* explicitly or by the FEP-ef61 gateway request being handled.
|
|
6067
|
+
* @throws {TypeError} If the authority is missing.
|
|
6068
|
+
*/
|
|
6069
|
+
function requirePortableAuthority(authority) {
|
|
6070
|
+
if (authority == null) throw new TypeError("The authority of a portable ID is required outside an FEP-ef61 gateway request.");
|
|
6071
|
+
return authority;
|
|
6072
|
+
}
|
|
6073
|
+
/**
|
|
5763
6074
|
* Removes the body of a response to a `HEAD` request.
|
|
5764
6075
|
* @param request The request.
|
|
5765
6076
|
* @param response The response to the request.
|
|
@@ -5938,8 +6249,7 @@ var ContextImpl = class ContextImpl {
|
|
|
5938
6249
|
}
|
|
5939
6250
|
getPortableActorUri(identifier, authority) {
|
|
5940
6251
|
const path = this.#getActorPath(identifier);
|
|
5941
|
-
|
|
5942
|
-
return buildPortableUri(authority, path);
|
|
6252
|
+
return buildPortableUri(requirePortableAuthority(authority), path);
|
|
5943
6253
|
}
|
|
5944
6254
|
#getActorPath(identifier) {
|
|
5945
6255
|
const path = this.federation.router.build(`actorAlias:${identifier}`, {}) ?? this.federation.router.build("actor", { identifier });
|
|
@@ -5951,8 +6261,7 @@ var ContextImpl = class ContextImpl {
|
|
|
5951
6261
|
}
|
|
5952
6262
|
getPortableObjectUri(cls, values, authority) {
|
|
5953
6263
|
const path = this.#getObjectPath(cls, values);
|
|
5954
|
-
|
|
5955
|
-
return buildPortableUri(authority, path);
|
|
6264
|
+
return buildPortableUri(requirePortableAuthority(authority), path);
|
|
5956
6265
|
}
|
|
5957
6266
|
#getObjectPath(cls, values) {
|
|
5958
6267
|
const callbacks = this.federation.objectCallbacks[cls.typeId.href];
|
|
@@ -5962,10 +6271,17 @@ var ContextImpl = class ContextImpl {
|
|
|
5962
6271
|
if (path == null) throw new RouterError("No object dispatcher registered.");
|
|
5963
6272
|
return path;
|
|
5964
6273
|
}
|
|
6274
|
+
#getActorCollectionPath(route, identifier) {
|
|
6275
|
+
const path = this.federation.router.build(route, { identifier });
|
|
6276
|
+
if (path == null) throw new RouterError(ACTOR_COLLECTION_ROUTE_ERRORS[route]);
|
|
6277
|
+
return path;
|
|
6278
|
+
}
|
|
5965
6279
|
getOutboxUri(identifier) {
|
|
5966
|
-
|
|
5967
|
-
|
|
5968
|
-
|
|
6280
|
+
return new URL(this.#getActorCollectionPath("outbox", identifier), this.canonicalOrigin);
|
|
6281
|
+
}
|
|
6282
|
+
getPortableOutboxUri(identifier, authority) {
|
|
6283
|
+
const path = this.#getActorCollectionPath("outbox", identifier);
|
|
6284
|
+
return buildPortableUri(requirePortableAuthority(authority), path);
|
|
5969
6285
|
}
|
|
5970
6286
|
getMediaUploaderUri(identifier) {
|
|
5971
6287
|
const path = this.federation.router.build("mediaUploader", { identifier });
|
|
@@ -5985,39 +6301,53 @@ var ContextImpl = class ContextImpl {
|
|
|
5985
6301
|
getPortableInboxUri(identifier, authority) {
|
|
5986
6302
|
const path = this.federation.router.build("inbox", { identifier });
|
|
5987
6303
|
if (path == null) throw new RouterError("No inbox path registered.");
|
|
5988
|
-
|
|
5989
|
-
return buildPortableUri(authority, path);
|
|
6304
|
+
return buildPortableUri(requirePortableAuthority(authority), path);
|
|
5990
6305
|
}
|
|
5991
6306
|
getFollowingUri(identifier) {
|
|
5992
|
-
|
|
5993
|
-
|
|
5994
|
-
|
|
6307
|
+
return new URL(this.#getActorCollectionPath("following", identifier), this.canonicalOrigin);
|
|
6308
|
+
}
|
|
6309
|
+
getPortableFollowingUri(identifier, authority) {
|
|
6310
|
+
const path = this.#getActorCollectionPath("following", identifier);
|
|
6311
|
+
return buildPortableUri(requirePortableAuthority(authority), path);
|
|
5995
6312
|
}
|
|
5996
6313
|
getFollowersUri(identifier) {
|
|
5997
|
-
|
|
5998
|
-
|
|
5999
|
-
|
|
6314
|
+
return new URL(this.#getActorCollectionPath("followers", identifier), this.canonicalOrigin);
|
|
6315
|
+
}
|
|
6316
|
+
getPortableFollowersUri(identifier, authority) {
|
|
6317
|
+
const path = this.#getActorCollectionPath("followers", identifier);
|
|
6318
|
+
return buildPortableUri(requirePortableAuthority(authority), path);
|
|
6000
6319
|
}
|
|
6001
6320
|
getLikedUri(identifier) {
|
|
6002
|
-
|
|
6003
|
-
|
|
6004
|
-
|
|
6321
|
+
return new URL(this.#getActorCollectionPath("liked", identifier), this.canonicalOrigin);
|
|
6322
|
+
}
|
|
6323
|
+
getPortableLikedUri(identifier, authority) {
|
|
6324
|
+
const path = this.#getActorCollectionPath("liked", identifier);
|
|
6325
|
+
return buildPortableUri(requirePortableAuthority(authority), path);
|
|
6005
6326
|
}
|
|
6006
6327
|
getFeaturedUri(identifier) {
|
|
6007
|
-
|
|
6008
|
-
|
|
6009
|
-
|
|
6328
|
+
return new URL(this.#getActorCollectionPath("featured", identifier), this.canonicalOrigin);
|
|
6329
|
+
}
|
|
6330
|
+
getPortableFeaturedUri(identifier, authority) {
|
|
6331
|
+
const path = this.#getActorCollectionPath("featured", identifier);
|
|
6332
|
+
return buildPortableUri(requirePortableAuthority(authority), path);
|
|
6010
6333
|
}
|
|
6011
6334
|
getFeaturedTagsUri(identifier) {
|
|
6012
|
-
|
|
6013
|
-
|
|
6014
|
-
|
|
6335
|
+
return new URL(this.#getActorCollectionPath("featuredTags", identifier), this.canonicalOrigin);
|
|
6336
|
+
}
|
|
6337
|
+
getPortableFeaturedTagsUri(identifier, authority) {
|
|
6338
|
+
const path = this.#getActorCollectionPath("featuredTags", identifier);
|
|
6339
|
+
return buildPortableUri(requirePortableAuthority(authority), path);
|
|
6015
6340
|
}
|
|
6016
6341
|
getCollectionUri(name, values) {
|
|
6017
6342
|
const path = this.federation.getCollectionPath(name, values);
|
|
6018
6343
|
if (path === null) throw new RouterError(`No collection dispatcher registered for "${String(name)}".`);
|
|
6019
6344
|
return new URL(path, this.canonicalOrigin);
|
|
6020
6345
|
}
|
|
6346
|
+
getPortableCollectionUri(name, values, authority) {
|
|
6347
|
+
const path = this.federation.getCollectionPath(name, values);
|
|
6348
|
+
if (path === null) throw new RouterError(`No collection dispatcher registered for "${String(name)}".`);
|
|
6349
|
+
return buildPortableUri(requirePortableAuthority(authority), path);
|
|
6350
|
+
}
|
|
6021
6351
|
parseUri(uri) {
|
|
6022
6352
|
if (uri == null) return null;
|
|
6023
6353
|
if (uri.origin !== this.origin && uri.origin !== this.canonicalOrigin) return null;
|
|
@@ -6597,6 +6927,27 @@ var RequestContextImpl = class RequestContextImpl extends ContextImpl {
|
|
|
6597
6927
|
getPortableInboxUri(identifier, authority) {
|
|
6598
6928
|
return super.getPortableInboxUri(identifier, authority ?? this.portableRequest?.authority);
|
|
6599
6929
|
}
|
|
6930
|
+
getPortableOutboxUri(identifier, authority) {
|
|
6931
|
+
return super.getPortableOutboxUri(identifier, authority ?? this.portableRequest?.authority);
|
|
6932
|
+
}
|
|
6933
|
+
getPortableFollowingUri(identifier, authority) {
|
|
6934
|
+
return super.getPortableFollowingUri(identifier, authority ?? this.portableRequest?.authority);
|
|
6935
|
+
}
|
|
6936
|
+
getPortableFollowersUri(identifier, authority) {
|
|
6937
|
+
return super.getPortableFollowersUri(identifier, authority ?? this.portableRequest?.authority);
|
|
6938
|
+
}
|
|
6939
|
+
getPortableLikedUri(identifier, authority) {
|
|
6940
|
+
return super.getPortableLikedUri(identifier, authority ?? this.portableRequest?.authority);
|
|
6941
|
+
}
|
|
6942
|
+
getPortableFeaturedUri(identifier, authority) {
|
|
6943
|
+
return super.getPortableFeaturedUri(identifier, authority ?? this.portableRequest?.authority);
|
|
6944
|
+
}
|
|
6945
|
+
getPortableFeaturedTagsUri(identifier, authority) {
|
|
6946
|
+
return super.getPortableFeaturedTagsUri(identifier, authority ?? this.portableRequest?.authority);
|
|
6947
|
+
}
|
|
6948
|
+
getPortableCollectionUri(name, values, authority) {
|
|
6949
|
+
return super.getPortableCollectionUri(name, values, authority ?? this.portableRequest?.authority);
|
|
6950
|
+
}
|
|
6600
6951
|
async getActor(identifier, options) {
|
|
6601
6952
|
if (this.federation.actorCallbacks == null || this.federation.actorCallbacks.dispatcher == null) throw new Error("No actor dispatcher registered.");
|
|
6602
6953
|
if (this.#invokedFromActorDispatcher != null) getLogger([
|