@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
|
@@ -2,15 +2,15 @@ import { Temporal } from "temporal-polyfill";
|
|
|
2
2
|
import { URLPattern } from "urlpattern-polyfill";
|
|
3
3
|
import { t as __exportAll } from "./rolldown-runtime-BC-Eo5Mj.js";
|
|
4
4
|
import { r as getDefaultActivityTransformers } from "./transformers-DAtXl_Kc.js";
|
|
5
|
-
import { $ as
|
|
6
|
-
import { a as MAX_BODY_SIZE, i as BodyTooLargeError, n as getNodeInfo, o as readBoundedText, t as nodeInfoToJson } from "./types-
|
|
7
|
-
import { n as doesActorOwnKey, r as getKeyOwner, t as verifyPortableObject } from "./portable-collection-
|
|
8
|
-
import { n as getAuthenticatedDocumentLoader, t as kvCache } from "./kv-cache-
|
|
5
|
+
import { $ as recordCollectionPageItems, A as hasSignatureLike, C as assertSafeJsonLd, D as detachSignature, J as getRemoteHost, K as getDurationMs, L as wrapContextLoaderForJsonLd, M as isInvalidUrlTypeError, N as signJsonLd, O as getNormalizationContextLoader, P as verifyCompactJsonLd, Q as recordCollectionDispatchDuration, R as exportJwk, S as InvalidContextReferenceError, St as isPortableUri, T as compactJsonLd, Tt as version, U as importJwk, W as validateCryptoKey, X as isAbortError, Y as instrumentDocumentLoader, Z as recordCircuitBreakerStateChange, _ as verifyPortableObjectProof, _t as getRawCanonicalPortableId, at as recordOutboxActivity, bt as isPortableId, ct as registerQueueDepthGauge, d as classifyFep2277CoreType, et as recordCollectionRequest, g as verifyObject, gt as getPortableDid, h as verifyMapLocalProof, ht as getGatewayKeyBase, i as selectRequestSignature, it as recordInboxActivity, j as isClearlyMalformedContextReference, k as hasSignature, lt as preloadedOnlyDocumentLoader, m as signObject, mt as getCanonicalPortableId, n as listRequestSignatures, o as verifyRequest, ot as recordOutboxEnqueue, p as hasProofLike, q as getFederationMetrics, r as parseRfc9421SignatureInput, rt as recordFanoutRecipients, s as verifyRequestDetailed, st as recordWebFingerHandle, t as doubleKnock, tt as recordCollectionTotalItems, u as InvalidPortableObjectIdError, ut as formatAcceptSignature, v as verifyPortableObjectProofPolicy, vt as hasPortableActor, wt as name, x as normalizeOutgoingActivityJsonLd, xt as isPortableKeyId, y as verifyPortableObjectProofWithRoot, yt as isCompatibleKeyId } from "./http-Dgtt45-K.js";
|
|
6
|
+
import { a as MAX_BODY_SIZE, i as BodyTooLargeError, n as getNodeInfo, o as readBoundedText, t as nodeInfoToJson } from "./types-_S2_MeLF.js";
|
|
7
|
+
import { n as doesActorOwnKey, r as getKeyOwner, t as verifyPortableObject } from "./portable-collection-C3xEzYri.js";
|
|
8
|
+
import { n as getAuthenticatedDocumentLoader, t as kvCache } from "./kv-cache-BweyIZTb.js";
|
|
9
9
|
import { getLogger, withContext } from "@logtape/logtape";
|
|
10
10
|
import { Router, RouterError, assertPath } from "@fedify/uri-template";
|
|
11
|
-
import { Activity, Collection, CollectionPage, CryptographicKey, Link, Multikey, Object as Object$1, OrderedCollection, OrderedCollectionPage, Tombstone, getTypeId, lookupObject, traverseCollection } from "@fedify/vocab";
|
|
11
|
+
import { Activity, Collection, CollectionPage, CryptographicKey, Link, Multikey, Object as Object$1, OrderedCollection, OrderedCollectionPage, Tombstone, getTypeId, isActor, lookupObject, traverseCollection } from "@fedify/vocab";
|
|
12
12
|
import { SpanKind, SpanStatusCode, context, metrics, propagation, trace } from "@opentelemetry/api";
|
|
13
|
-
import { FetchError, UrlError, canonicalizePortableUri, decodeMultibase, formatIri, fromCompatibleEf61Id, getDocumentLoader, getFe34Origin, isGatewayUrl, parseDigestMultibase, parseHashlink, parseIri, toCompatibleEf61Id, validatePublicUrl } from "@fedify/vocab-runtime";
|
|
13
|
+
import { FetchError, UrlError, canonicalizePortableUri, decodeMultibase, formatIri, fromCompatibleEf61Id, getDocumentLoader, getFe34Origin, isGatewayUrl, parseDigestMultibase, parseHashlink, parseIri, preloadedContexts, toCompatibleEf61Id, validatePublicUrl } from "@fedify/vocab-runtime";
|
|
14
14
|
import { isCompatibleEf61Iri } from "@fedify/vocab-runtime/internal/portable-dereference";
|
|
15
15
|
import { encodeHex } from "byte-encodings/hex";
|
|
16
16
|
import jsonld from "@fedify/vocab-runtime/jsonld";
|
|
@@ -211,6 +211,7 @@ var FederationBuilderImpl = class {
|
|
|
211
211
|
else if (!portable && actor.id.href != context.getActorUri(identifier).href) logger.warn("Actor dispatcher returned an actor with an id property that does not match the actor URI. Set the property with Context.getActorUri(identifier).");
|
|
212
212
|
if (actor instanceof Tombstone) return actor;
|
|
213
213
|
if (actor.id != null && isCompatibleEf61Iri(actor.id)) warnCompatibleActorId(actor.id, actor.gateway);
|
|
214
|
+
if (portable) this.#warnPortableCollectionIds(context, identifier, actor);
|
|
214
215
|
if (this.followingCallbacks != null && this.followingCallbacks.dispatcher != null) {
|
|
215
216
|
if (actor.followingId == null) logger.warn("You configured a following collection dispatcher, but the actor does not have a following property. Set the property with Context.getFollowingUri(identifier).");
|
|
216
217
|
else if (!portable && actor.followingId.href != context.getFollowingUri(identifier).href) logger.warn("You configured a following collection dispatcher, but the actor's following property does not match the following collection URI. Set the property with Context.getFollowingUri(identifier).");
|
|
@@ -696,6 +697,10 @@ var FederationBuilderImpl = class {
|
|
|
696
697
|
authorize(predicate) {
|
|
697
698
|
callbacks.authorizePredicate = predicate;
|
|
698
699
|
return setters;
|
|
700
|
+
},
|
|
701
|
+
mapPortableOwner(mapper) {
|
|
702
|
+
callbacks.portableOwnerMapper = mapper;
|
|
703
|
+
return setters;
|
|
699
704
|
}
|
|
700
705
|
};
|
|
701
706
|
return setters;
|
|
@@ -717,6 +722,54 @@ var FederationBuilderImpl = class {
|
|
|
717
722
|
this.outboxPermanentFailureHandler = handler;
|
|
718
723
|
}
|
|
719
724
|
/**
|
|
725
|
+
* Warns if a portable actor's collection properties that are portable IDs
|
|
726
|
+
* or compatible identifiers do not refer to the collections that
|
|
727
|
+
* the registered collection dispatchers serve through the FEP-ef61 gateway
|
|
728
|
+
* endpoint for the actor's DID. Properties with ordinary HTTP(S) URLs are
|
|
729
|
+
* not compared, since a portable actor may keep ordinary collections.
|
|
730
|
+
*/
|
|
731
|
+
#warnPortableCollectionIds(context, identifier, actor) {
|
|
732
|
+
const did = actor.id == null ? null : getPortableDid(actor.id);
|
|
733
|
+
if (did == null) return;
|
|
734
|
+
const logger = getLogger([
|
|
735
|
+
"fedify",
|
|
736
|
+
"federation",
|
|
737
|
+
"actor"
|
|
738
|
+
]);
|
|
739
|
+
const check = (registered, property, value, helper, build) => {
|
|
740
|
+
if (!registered || value == null || !isPortableId(value)) return;
|
|
741
|
+
const actual = getCanonicalPortableId(value);
|
|
742
|
+
if (actual == null) {
|
|
743
|
+
logger.warn("The actor's {property} property, {value}, is a malformed FEP-ef61 portable ID or compatible identifier.", {
|
|
744
|
+
property,
|
|
745
|
+
value: value.href
|
|
746
|
+
});
|
|
747
|
+
return;
|
|
748
|
+
}
|
|
749
|
+
let expected;
|
|
750
|
+
try {
|
|
751
|
+
expected = build();
|
|
752
|
+
} catch (error) {
|
|
753
|
+
if (error instanceof TypeError || error instanceof RouterError) return;
|
|
754
|
+
throw error;
|
|
755
|
+
}
|
|
756
|
+
if (actual === getCanonicalPortableId(expected)) return;
|
|
757
|
+
logger.warn("The portable actor's {property} property, {value}, does not match the portable ID of the collection that the gateway endpoint serves, {expected}. Set the property with Context.{helper}(identifier, did).", {
|
|
758
|
+
property,
|
|
759
|
+
value: value.href,
|
|
760
|
+
expected: expected.href,
|
|
761
|
+
helper
|
|
762
|
+
});
|
|
763
|
+
};
|
|
764
|
+
check(this.outboxCallbacks?.dispatcher != null, "outbox", actor.outboxId, "getPortableOutboxUri", () => context.getPortableOutboxUri(identifier, did));
|
|
765
|
+
check(this.inboxCallbacks?.dispatcher != null || this.router.has("inbox"), "inbox", actor.inboxId, "getPortableInboxUri", () => context.getPortableInboxUri(identifier, did));
|
|
766
|
+
check(this.followingCallbacks?.dispatcher != null, "following", actor.followingId, "getPortableFollowingUri", () => context.getPortableFollowingUri(identifier, did));
|
|
767
|
+
check(this.followersCallbacks?.dispatcher != null, "followers", actor.followersId, "getPortableFollowersUri", () => context.getPortableFollowersUri(identifier, did));
|
|
768
|
+
check(this.likedCallbacks?.dispatcher != null, "liked", actor.likedId, "getPortableLikedUri", () => context.getPortableLikedUri(identifier, did));
|
|
769
|
+
check(this.featuredCallbacks?.dispatcher != null, "featured", actor.featuredId, "getPortableFeaturedUri", () => context.getPortableFeaturedUri(identifier, did));
|
|
770
|
+
check(this.featuredTagsCallbacks?.dispatcher != null, "featuredTags", actor.featuredTagsId, "getPortableFeaturedTagsUri", () => context.getPortableFeaturedTagsUri(identifier, did));
|
|
771
|
+
}
|
|
772
|
+
/**
|
|
720
773
|
* Converts a name (string or symbol) to a unique string identifier.
|
|
721
774
|
* For symbols, generates and caches a UUID if not already present.
|
|
722
775
|
* For strings, returns the string as-is.
|
|
@@ -2067,6 +2120,218 @@ function isSingleStringValue(values, datatype) {
|
|
|
2067
2120
|
const keys = Object.keys(value);
|
|
2068
2121
|
return keys.length === 1 || datatype != null && keys.length === 2 && value["@type"] === datatype;
|
|
2069
2122
|
}
|
|
2123
|
+
/**
|
|
2124
|
+
* FEP-2277 core types that FEP-ef61 does not require to carry proofs.
|
|
2125
|
+
*/
|
|
2126
|
+
const UNSECURED_CORE_TYPES = /* @__PURE__ */ new Set([
|
|
2127
|
+
"collection",
|
|
2128
|
+
"link",
|
|
2129
|
+
"verificationMethod",
|
|
2130
|
+
"publicKey"
|
|
2131
|
+
]);
|
|
2132
|
+
/**
|
|
2133
|
+
* Applies the FEP-ef61 proof policy, under the map-local compound-proof
|
|
2134
|
+
* profile, to every portable object embedded in a document that a gateway is
|
|
2135
|
+
* about to serve, e.g., the items of a portable collection page. The root
|
|
2136
|
+
* map itself is not checked, as the caller built it.
|
|
2137
|
+
*
|
|
2138
|
+
* Each map below the root whose direct `id` or `@id` is a portable ID or
|
|
2139
|
+
* a compatible identifier is checked independently of the others:
|
|
2140
|
+
*
|
|
2141
|
+
* - A map with a direct `proof` is a separately secured document: the proof
|
|
2142
|
+
* has to be a single map, the map has to carry its own `@context`, and
|
|
2143
|
+
* the proof has to verify over the map exactly as it is served, with only
|
|
2144
|
+
* its own proof removed, and be made with a key of the DID in the map's
|
|
2145
|
+
* ID, as the map-local compound-proof profile requires.
|
|
2146
|
+
* - A map without a proof has to be a collection, a link, or a key, which
|
|
2147
|
+
* FEP-ef61 does not require to be secured. It is classified under its
|
|
2148
|
+
* effective context, i.e., the `@context` values of its ancestors and
|
|
2149
|
+
* itself, which therefore may only use the simple, preloaded contexts
|
|
2150
|
+
* that the compound-proof policy accepts.
|
|
2151
|
+
*
|
|
2152
|
+
* Maps that merely refer to a portable object, i.e., have no member other
|
|
2153
|
+
* than `id` or `@id`, and maps with non-portable IDs, are neither checked nor
|
|
2154
|
+
* verified. Every `@context` in the document, however, has to be simple, and
|
|
2155
|
+
* every portable object that the expanded document describes has to be
|
|
2156
|
+
* a map found this way, so that no context can hide a portable object from
|
|
2157
|
+
* this check, e.g., by aliasing `@id` or redefining `proof`. The input must be a finite, already-materialized JSON tree, such
|
|
2158
|
+
* as the compact JSON-LD document that is about to be served.
|
|
2159
|
+
*
|
|
2160
|
+
* @param json The document to check.
|
|
2161
|
+
* @param options The options for verifying the proofs. Proofs whose
|
|
2162
|
+
* verification methods are not `did:key` DID URLs may make
|
|
2163
|
+
* the document loader fetch DID documents.
|
|
2164
|
+
* @returns The result, which names the first refused map, if any.
|
|
2165
|
+
* @internal
|
|
2166
|
+
*/
|
|
2167
|
+
async function verifyServedPortableObjects(json, options = {}) {
|
|
2168
|
+
json = JSON.parse(JSON.stringify(json));
|
|
2169
|
+
const collected = collectServedPortableMaps(json);
|
|
2170
|
+
if (!("maps" in collected)) return collected;
|
|
2171
|
+
const maps = collected.maps;
|
|
2172
|
+
const discovered = /* @__PURE__ */ new Map();
|
|
2173
|
+
const ids = maps.map((candidate) => candidate.id);
|
|
2174
|
+
const rootId = isJsonMap(json) ? [json.id, json["@id"]].find(isPortableIdValue) : void 0;
|
|
2175
|
+
if (rootId != null) ids.push(rootId);
|
|
2176
|
+
for (const id of ids) discovered.set(id, (discovered.get(id) ?? 0) + 1);
|
|
2177
|
+
let hidden;
|
|
2178
|
+
try {
|
|
2179
|
+
hidden = findHiddenPortableObject(await jsonld.expand(json, { documentLoader: preloadedOnlyDocumentLoader }), discovered);
|
|
2180
|
+
} catch {
|
|
2181
|
+
return {
|
|
2182
|
+
verified: false,
|
|
2183
|
+
path: "",
|
|
2184
|
+
id: "",
|
|
2185
|
+
reason: "unsupportedContext"
|
|
2186
|
+
};
|
|
2187
|
+
}
|
|
2188
|
+
if (hidden != null) return {
|
|
2189
|
+
verified: false,
|
|
2190
|
+
path: "",
|
|
2191
|
+
id: hidden,
|
|
2192
|
+
reason: "hiddenPortableObject"
|
|
2193
|
+
};
|
|
2194
|
+
for (const candidate of maps) {
|
|
2195
|
+
const reason = await checkServedPortableMap(candidate, options);
|
|
2196
|
+
if (reason != null) return {
|
|
2197
|
+
verified: false,
|
|
2198
|
+
path: candidate.path,
|
|
2199
|
+
id: candidate.id,
|
|
2200
|
+
reason
|
|
2201
|
+
};
|
|
2202
|
+
}
|
|
2203
|
+
return { verified: true };
|
|
2204
|
+
}
|
|
2205
|
+
/**
|
|
2206
|
+
* Finds a portable object that an expanded JSON-LD document describes, i.e.,
|
|
2207
|
+
* a node with a portable `@id` and other members, more times than the given
|
|
2208
|
+
* numbers of maps found for its ID.
|
|
2209
|
+
*/
|
|
2210
|
+
function findHiddenPortableObject(expanded, discovered) {
|
|
2211
|
+
const remaining = new Map(discovered);
|
|
2212
|
+
const pending = [expanded];
|
|
2213
|
+
while (pending.length > 0) {
|
|
2214
|
+
const value = pending.pop();
|
|
2215
|
+
if (Array.isArray(value)) {
|
|
2216
|
+
pending.push(...value);
|
|
2217
|
+
continue;
|
|
2218
|
+
}
|
|
2219
|
+
if (!isJsonMap(value)) continue;
|
|
2220
|
+
const id = value["@id"];
|
|
2221
|
+
if (isPortableIdValue(id) && Object.keys(value).some((key) => key !== "@id")) {
|
|
2222
|
+
const count = remaining.get(id) ?? 0;
|
|
2223
|
+
if (count < 1) return id;
|
|
2224
|
+
remaining.set(id, count - 1);
|
|
2225
|
+
}
|
|
2226
|
+
pending.push(...Object.values(value));
|
|
2227
|
+
}
|
|
2228
|
+
}
|
|
2229
|
+
/**
|
|
2230
|
+
* Checks whether a context uses only preloaded remote contexts and simple
|
|
2231
|
+
* term definitions, so that the context in effect for a map can be told from
|
|
2232
|
+
* the contexts of its ancestors.
|
|
2233
|
+
*/
|
|
2234
|
+
function isCheckableContext(context) {
|
|
2235
|
+
return (Array.isArray(context) ? context : [context]).every((value) => typeof value === "string" ? Object.hasOwn(preloadedContexts, value) : !Array.isArray(value) && isSimpleContext(value));
|
|
2236
|
+
}
|
|
2237
|
+
/**
|
|
2238
|
+
* Checks whether every remote context that a context refers to is preloaded,
|
|
2239
|
+
* so that all of its definitions are known.
|
|
2240
|
+
*/
|
|
2241
|
+
function hasOnlyPreloadedRemoteContexts(context) {
|
|
2242
|
+
return (Array.isArray(context) ? context : [context]).every((value) => typeof value === "string" ? Object.hasOwn(preloadedContexts, value) : !Array.isArray(value));
|
|
2243
|
+
}
|
|
2244
|
+
/**
|
|
2245
|
+
* Checks whether a map or any map below it, outside proof and context values,
|
|
2246
|
+
* has a portable `id` or `@id` and other members.
|
|
2247
|
+
*/
|
|
2248
|
+
function hasLiteralPortableObject(json) {
|
|
2249
|
+
return findInJsonMaps(json, (map) => [map.id, map["@id"]].some(isPortableIdValue) && Object.keys(map).some((key) => key !== "id" && key !== "@id") ? true : void 0) ?? false;
|
|
2250
|
+
}
|
|
2251
|
+
function collectServedPortableMaps(json) {
|
|
2252
|
+
const maps = [];
|
|
2253
|
+
const seen = /* @__PURE__ */ new Set();
|
|
2254
|
+
const pending = [{
|
|
2255
|
+
value: json,
|
|
2256
|
+
path: "",
|
|
2257
|
+
depth: 0,
|
|
2258
|
+
contexts: []
|
|
2259
|
+
}];
|
|
2260
|
+
while (pending.length > 0) {
|
|
2261
|
+
const { value, path, depth, contexts } = pending.pop();
|
|
2262
|
+
if (typeof value !== "object" || value == null || seen.has(value)) continue;
|
|
2263
|
+
seen.add(value);
|
|
2264
|
+
if (Array.isArray(value)) {
|
|
2265
|
+
for (let index = value.length - 1; index >= 0; index--) pending.push({
|
|
2266
|
+
value: value[index],
|
|
2267
|
+
path: childPath(path, String(index)),
|
|
2268
|
+
depth: depth + 1,
|
|
2269
|
+
contexts
|
|
2270
|
+
});
|
|
2271
|
+
continue;
|
|
2272
|
+
}
|
|
2273
|
+
const map = value;
|
|
2274
|
+
const hasContext = Object.hasOwn(map, "@context");
|
|
2275
|
+
if (hasContext && !isCheckableContext(map["@context"])) {
|
|
2276
|
+
if (hasOnlyPreloadedRemoteContexts(map["@context"]) && !hasLiteralPortableObject(map)) continue;
|
|
2277
|
+
const id = [map.id, map["@id"]].find((v) => typeof v === "string");
|
|
2278
|
+
return {
|
|
2279
|
+
verified: false,
|
|
2280
|
+
path,
|
|
2281
|
+
id: typeof id === "string" ? id : "",
|
|
2282
|
+
reason: "unsupportedContext"
|
|
2283
|
+
};
|
|
2284
|
+
}
|
|
2285
|
+
const mapContexts = hasContext ? [...contexts, map["@context"]] : contexts;
|
|
2286
|
+
const id = [map.id, map["@id"]].find(isPortableIdValue);
|
|
2287
|
+
if (depth > 0 && id != null && Object.keys(map).some((key) => key !== "id" && key !== "@id")) maps.push({
|
|
2288
|
+
path,
|
|
2289
|
+
id,
|
|
2290
|
+
map,
|
|
2291
|
+
contexts: mapContexts
|
|
2292
|
+
});
|
|
2293
|
+
const keys = Object.keys(map);
|
|
2294
|
+
for (let index = keys.length - 1; index >= 0; index--) {
|
|
2295
|
+
const key = keys[index];
|
|
2296
|
+
if (key === "proof" || key === "@context") continue;
|
|
2297
|
+
pending.push({
|
|
2298
|
+
value: map[key],
|
|
2299
|
+
path: childPath(path, key),
|
|
2300
|
+
depth: depth + 1,
|
|
2301
|
+
contexts: mapContexts
|
|
2302
|
+
});
|
|
2303
|
+
}
|
|
2304
|
+
}
|
|
2305
|
+
maps.sort((left, right) => comparePaths(left.path, right.path));
|
|
2306
|
+
return { maps };
|
|
2307
|
+
}
|
|
2308
|
+
async function checkServedPortableMap({ map, contexts }, options) {
|
|
2309
|
+
if (Object.hasOwn(map, "proof")) {
|
|
2310
|
+
if (!isJsonMap(map.proof)) return "unsupportedProofShape";
|
|
2311
|
+
if (!Object.hasOwn(map, "@context")) return "missingContext";
|
|
2312
|
+
try {
|
|
2313
|
+
const policy = await verifyPortableObjectProofPolicy(map, await verifyMapLocalProof(map, options), options);
|
|
2314
|
+
return policy.verified ? null : policy.reason.type;
|
|
2315
|
+
} catch (error) {
|
|
2316
|
+
if (error instanceof TypeError) return "invalidPortableObject";
|
|
2317
|
+
throw error;
|
|
2318
|
+
}
|
|
2319
|
+
}
|
|
2320
|
+
const flattened = contexts.flatMap((context) => Array.isArray(context) ? context : [context]);
|
|
2321
|
+
if (flattened.length < 1 || !isSimpleContext(flattened)) return "unsupportedContext";
|
|
2322
|
+
let expanded;
|
|
2323
|
+
try {
|
|
2324
|
+
expanded = await jsonld.expand({
|
|
2325
|
+
...map,
|
|
2326
|
+
"@context": flattened
|
|
2327
|
+
}, { documentLoader: preloadedOnlyDocumentLoader });
|
|
2328
|
+
} catch {
|
|
2329
|
+
return "unsupportedContext";
|
|
2330
|
+
}
|
|
2331
|
+
if (expanded.length !== 1 || !isJsonMap(expanded[0])) return "invalidPortableObject";
|
|
2332
|
+
const type = classifyFep2277CoreType(expanded[0]);
|
|
2333
|
+
return UNSECURED_CORE_TYPES.has(type) ? null : "missingProof";
|
|
2334
|
+
}
|
|
2070
2335
|
//#endregion
|
|
2071
2336
|
//#region src/federation/inbox.ts
|
|
2072
2337
|
async function routeActivity({ context: ctx, json, originalJson, normalizedActivity, ldSignatureVerified, activity, recipient, inboxListeners, inboxContextFactory, listenerInboxContextFactory, inboxErrorHandler, kv, kvPrefixes, queue, span, meterProvider, tracerProvider, idempotencyStrategy }) {
|
|
@@ -2748,6 +3013,15 @@ async function sendActivityInternal({ activity, activityId, activityType, keys,
|
|
|
2748
3013
|
if (!allowPrivateAddress) try {
|
|
2749
3014
|
await validatePublicUrl(url);
|
|
2750
3015
|
} catch (error) {
|
|
3016
|
+
if (error instanceof UrlError && error.reason === "dns") {
|
|
3017
|
+
logger.error("DNS resolution failed for URL: {url}", {
|
|
3018
|
+
url,
|
|
3019
|
+
error
|
|
3020
|
+
});
|
|
3021
|
+
const failure = new FetchError(url, error.message);
|
|
3022
|
+
failure.cause = error;
|
|
3023
|
+
throw failure;
|
|
3024
|
+
}
|
|
2751
3025
|
if (error instanceof UrlError) logger.error("Disallowed private URL: {url}", {
|
|
2752
3026
|
url,
|
|
2753
3027
|
error
|
|
@@ -2755,7 +3029,12 @@ async function sendActivityInternal({ activity, activityId, activityType, keys,
|
|
|
2755
3029
|
throw error;
|
|
2756
3030
|
}
|
|
2757
3031
|
}
|
|
2758
|
-
|
|
3032
|
+
try {
|
|
3033
|
+
await validateUrl(inbox.href);
|
|
3034
|
+
} catch (error) {
|
|
3035
|
+
if (error instanceof FetchError) federationMetrics.recordDelivery(inbox, getDurationMs(started), false, activityType);
|
|
3036
|
+
throw error;
|
|
3037
|
+
}
|
|
2759
3038
|
headers = new Headers(headers);
|
|
2760
3039
|
headers.set("Content-Type", "application/activity+json");
|
|
2761
3040
|
const request = new Request(inbox, {
|
|
@@ -3626,6 +3905,46 @@ function portableObjectInternalServerError(request) {
|
|
|
3626
3905
|
headers: { "Content-Type": "text/plain; charset=utf-8" }
|
|
3627
3906
|
});
|
|
3628
3907
|
}
|
|
3908
|
+
/**
|
|
3909
|
+
* Checks the portable objects embedded in a portable collection or its page
|
|
3910
|
+
* that is about to be served against the FEP-ef61 proof policy. See
|
|
3911
|
+
* `verifyServedPortableObjects()`.
|
|
3912
|
+
* @returns `true` if the collection can be served.
|
|
3913
|
+
*/
|
|
3914
|
+
async function verifyPortableCollection(jsonLd, context, portable) {
|
|
3915
|
+
const logger = getLogger([
|
|
3916
|
+
"fedify",
|
|
3917
|
+
"federation",
|
|
3918
|
+
"collection"
|
|
3919
|
+
]);
|
|
3920
|
+
try {
|
|
3921
|
+
const result = await verifyServedPortableObjects(jsonLd, {
|
|
3922
|
+
contextLoader: context.contextLoader,
|
|
3923
|
+
documentLoader: context.documentLoader,
|
|
3924
|
+
tracerProvider: context.tracerProvider,
|
|
3925
|
+
meterProvider: context.meterProvider
|
|
3926
|
+
});
|
|
3927
|
+
if (result.verified) return true;
|
|
3928
|
+
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.", {
|
|
3929
|
+
collectionId: portable.id.href,
|
|
3930
|
+
objectId: result.id,
|
|
3931
|
+
path: result.path,
|
|
3932
|
+
reason: result.reason
|
|
3933
|
+
});
|
|
3934
|
+
} catch (error) {
|
|
3935
|
+
logger.error("Failed to verify the portable objects embedded in the portable collection {collectionId}:\n{error}", {
|
|
3936
|
+
collectionId: portable.id.href,
|
|
3937
|
+
error
|
|
3938
|
+
});
|
|
3939
|
+
}
|
|
3940
|
+
return false;
|
|
3941
|
+
}
|
|
3942
|
+
function respondWithPortableCollection(request, jsonLd) {
|
|
3943
|
+
return new Response(request.method === "HEAD" ? null : JSON.stringify(jsonLd), { headers: {
|
|
3944
|
+
"Content-Type": PORTABLE_OBJECT_CONTENT_TYPE,
|
|
3945
|
+
Vary: "Accept"
|
|
3946
|
+
} });
|
|
3947
|
+
}
|
|
3629
3948
|
const BUILT_IN_COLLECTION_METRIC_KINDS = /* @__PURE__ */ new Set([
|
|
3630
3949
|
"inbox",
|
|
3631
3950
|
"outbox",
|
|
@@ -3663,13 +3982,13 @@ function recordCollectionMetrics(meterProvider, base, result, options = {}) {
|
|
|
3663
3982
|
* @param parameters The parameters for handling the collection.
|
|
3664
3983
|
* @returns A promise that resolves to an HTTP response.
|
|
3665
3984
|
*/
|
|
3666
|
-
async function handleCollection(request, { name: name$
|
|
3667
|
-
const spanName = name$
|
|
3985
|
+
async function handleCollection(request, { name: name$1, identifier, uriGetter, filter, filterPredicate, context, collectionCallbacks, portable, tracerProvider, meterProvider, onUnauthorized, onNotFound }) {
|
|
3986
|
+
const spanName = name$1.trim().replace(/\s+/g, "_");
|
|
3668
3987
|
tracerProvider = tracerProvider ?? trace.getTracerProvider();
|
|
3669
3988
|
const tracer = tracerProvider.getTracer(name, version);
|
|
3670
3989
|
const cursor = new URL(request.url).searchParams.get("cursor");
|
|
3671
3990
|
const metricBase = {
|
|
3672
|
-
kind: getCollectionMetricKind(name$
|
|
3991
|
+
kind: getCollectionMetricKind(name$1),
|
|
3673
3992
|
page: cursor != null,
|
|
3674
3993
|
dispatcher: "built_in"
|
|
3675
3994
|
};
|
|
@@ -3687,8 +4006,13 @@ async function handleCollection(request, { name: name$2, identifier, uriGetter,
|
|
|
3687
4006
|
};
|
|
3688
4007
|
try {
|
|
3689
4008
|
if (collectionCallbacks == null) return finish(await onNotFound(request), "not_found");
|
|
4009
|
+
if (portable != null && collectionCallbacks.authorizePredicate != null) {
|
|
4010
|
+
if (!await collectionCallbacks.authorizePredicate(context, identifier)) return finish(await onUnauthorized(request), "unauthorized");
|
|
4011
|
+
}
|
|
3690
4012
|
let collection;
|
|
3691
|
-
const baseUri = uriGetter(identifier);
|
|
4013
|
+
const baseUri = portable?.id ?? uriGetter(identifier);
|
|
4014
|
+
const pageBase = portable?.view ?? context.url;
|
|
4015
|
+
const attribution = portable?.attribution ?? null;
|
|
3692
4016
|
if (cursor == null) {
|
|
3693
4017
|
const firstCursor = await collectionCallbacks.firstCursor?.(context, identifier);
|
|
3694
4018
|
const totalItems = filter == null ? await collectionCallbacks.counter?.(context, identifier) : void 0;
|
|
@@ -3710,7 +4034,7 @@ async function handleCollection(request, { name: name$2, identifier, uriGetter,
|
|
|
3710
4034
|
span.setStatus({ code: SpanStatusCode.ERROR });
|
|
3711
4035
|
return await onNotFound(request);
|
|
3712
4036
|
}
|
|
3713
|
-
const items = filterCollectionItems(page.items, name$
|
|
4037
|
+
const items = filterCollectionItems(page.items, name$1, filterPredicate);
|
|
3714
4038
|
itemCount = items.length;
|
|
3715
4039
|
span.setAttribute("fedify.collection.items", itemCount);
|
|
3716
4040
|
return items;
|
|
@@ -3728,29 +4052,31 @@ async function handleCollection(request, { name: name$2, identifier, uriGetter,
|
|
|
3728
4052
|
if (itemsOrResponse instanceof Response) return finish(itemsOrResponse, "not_found");
|
|
3729
4053
|
collection = new OrderedCollection({
|
|
3730
4054
|
id: baseUri,
|
|
4055
|
+
attribution,
|
|
3731
4056
|
totalItems: totalItemCount ?? null,
|
|
3732
4057
|
items: itemsOrResponse
|
|
3733
4058
|
});
|
|
3734
4059
|
} else {
|
|
3735
4060
|
const lastCursor = await collectionCallbacks.lastCursor?.(context, identifier);
|
|
3736
|
-
const first = new URL(
|
|
4061
|
+
const first = new URL(pageBase);
|
|
3737
4062
|
first.searchParams.set("cursor", firstCursor);
|
|
3738
4063
|
let last = null;
|
|
3739
4064
|
if (lastCursor != null) {
|
|
3740
|
-
last = new URL(
|
|
4065
|
+
last = new URL(pageBase);
|
|
3741
4066
|
last.searchParams.set("cursor", lastCursor);
|
|
3742
4067
|
}
|
|
3743
4068
|
collection = new OrderedCollection({
|
|
3744
4069
|
id: baseUri,
|
|
4070
|
+
attribution,
|
|
3745
4071
|
totalItems: totalItemCount ?? null,
|
|
3746
4072
|
first,
|
|
3747
4073
|
last
|
|
3748
4074
|
});
|
|
3749
4075
|
}
|
|
3750
4076
|
} else {
|
|
3751
|
-
const uri = new URL(baseUri);
|
|
4077
|
+
const uri = new URL(portable == null ? baseUri : pageBase);
|
|
3752
4078
|
uri.searchParams.set("cursor", cursor);
|
|
3753
|
-
const pageOrResponse = await tracer.startActiveSpan(`activitypub.dispatch_collection_page ${name$
|
|
4079
|
+
const pageOrResponse = await tracer.startActiveSpan(`activitypub.dispatch_collection_page ${name$1}`, {
|
|
3754
4080
|
kind: SpanKind.SERVER,
|
|
3755
4081
|
attributes: {
|
|
3756
4082
|
"activitypub.collection.id": uri.href,
|
|
@@ -3766,7 +4092,7 @@ async function handleCollection(request, { name: name$2, identifier, uriGetter,
|
|
|
3766
4092
|
span.setStatus({ code: SpanStatusCode.ERROR });
|
|
3767
4093
|
return await onNotFound(request);
|
|
3768
4094
|
}
|
|
3769
|
-
const items = filterCollectionItems(page.items, name$
|
|
4095
|
+
const items = filterCollectionItems(page.items, name$1, filterPredicate);
|
|
3770
4096
|
itemCount = items.length;
|
|
3771
4097
|
span.setAttribute("fedify.collection.items", itemCount);
|
|
3772
4098
|
return {
|
|
@@ -3788,28 +4114,30 @@ async function handleCollection(request, { name: name$2, identifier, uriGetter,
|
|
|
3788
4114
|
const { items, prevCursor, nextCursor } = pageOrResponse;
|
|
3789
4115
|
let prev = null;
|
|
3790
4116
|
if (prevCursor != null) {
|
|
3791
|
-
prev = new URL(
|
|
4117
|
+
prev = new URL(pageBase);
|
|
3792
4118
|
prev.searchParams.set("cursor", prevCursor);
|
|
3793
4119
|
}
|
|
3794
4120
|
let next = null;
|
|
3795
4121
|
if (nextCursor != null) {
|
|
3796
|
-
next = new URL(
|
|
4122
|
+
next = new URL(pageBase);
|
|
3797
4123
|
next.searchParams.set("cursor", nextCursor);
|
|
3798
4124
|
}
|
|
3799
|
-
const partOf = new URL(
|
|
4125
|
+
const partOf = new URL(pageBase);
|
|
3800
4126
|
partOf.searchParams.delete("cursor");
|
|
3801
4127
|
collection = new OrderedCollectionPage({
|
|
3802
4128
|
id: uri,
|
|
4129
|
+
attribution,
|
|
3803
4130
|
prev,
|
|
3804
4131
|
next,
|
|
3805
4132
|
items,
|
|
3806
4133
|
partOf
|
|
3807
4134
|
});
|
|
3808
4135
|
}
|
|
3809
|
-
if (collectionCallbacks.authorizePredicate != null) {
|
|
4136
|
+
if (portable == null && collectionCallbacks.authorizePredicate != null) {
|
|
3810
4137
|
if (!await collectionCallbacks.authorizePredicate(context, identifier)) return finish(await onUnauthorized(request), "unauthorized");
|
|
3811
4138
|
}
|
|
3812
4139
|
const jsonLd = await collection.toJsonLd(context);
|
|
4140
|
+
if (portable != null) return await verifyPortableCollection(jsonLd, context, portable) ? finish(respondWithPortableCollection(request, jsonLd), "served") : finish(portableObjectInternalServerError(request), "error");
|
|
3813
4141
|
return finish(new Response(JSON.stringify(jsonLd), { headers: {
|
|
3814
4142
|
"Content-Type": "application/activity+json",
|
|
3815
4143
|
Vary: "Accept"
|
|
@@ -4857,19 +5185,11 @@ function recordDeferredPendingCollectionMetrics(error, result, response) {
|
|
|
4857
5185
|
pendingCollectionMetricRecorders.delete(error);
|
|
4858
5186
|
recorder?.(result, response);
|
|
4859
5187
|
}
|
|
4860
|
-
async function _handleCustomCollection(request, { name, values, context, tracerProvider, meterProvider, collectionCallbacks: callbacks, filterPredicate }) {
|
|
5188
|
+
async function _handleCustomCollection(request, { name, values, context, tracerProvider, meterProvider, collectionCallbacks: callbacks, filterPredicate, portable }) {
|
|
4861
5189
|
verifyDefined(callbacks);
|
|
4862
5190
|
await authIfNeeded(context, values, callbacks);
|
|
4863
5191
|
const cursor = new URL(request.url).searchParams.get("cursor");
|
|
4864
|
-
|
|
4865
|
-
try {
|
|
4866
|
-
const response = await handler.toJsonLd().then(respondAsActivity);
|
|
4867
|
-
handler.recordPendingCollectionMetrics("served", response);
|
|
4868
|
-
return response;
|
|
4869
|
-
} catch (e) {
|
|
4870
|
-
if (!deferPendingCollectionMetrics(e, (result, response) => handler.recordPendingCollectionMetrics(result, response))) handler.recordPendingCollectionMetrics("error");
|
|
4871
|
-
throw e;
|
|
4872
|
-
}
|
|
5192
|
+
return await respondWithCustomCollection(request, new CustomCollectionHandler(name, values, context, callbacks, tracerProvider, meterProvider, Collection, CollectionPage, filterPredicate, portable).fetchCollection(cursor), context, portable);
|
|
4873
5193
|
}
|
|
4874
5194
|
/**
|
|
4875
5195
|
* Handles an ordered collection request.
|
|
@@ -4883,13 +5203,19 @@ async function _handleCustomCollection(request, { name, values, context, tracerP
|
|
|
4883
5203
|
* @since 1.8.0
|
|
4884
5204
|
*/
|
|
4885
5205
|
const handleOrderedCollection = exceptWrapper(_handleOrderedCollection);
|
|
4886
|
-
async function _handleOrderedCollection(request, { name, values, context, tracerProvider, meterProvider, collectionCallbacks: callbacks, filterPredicate }) {
|
|
5206
|
+
async function _handleOrderedCollection(request, { name, values, context, tracerProvider, meterProvider, collectionCallbacks: callbacks, filterPredicate, portable }) {
|
|
4887
5207
|
verifyDefined(callbacks);
|
|
4888
5208
|
await authIfNeeded(context, values, callbacks);
|
|
4889
5209
|
const cursor = new URL(request.url).searchParams.get("cursor");
|
|
4890
|
-
|
|
5210
|
+
return await respondWithCustomCollection(request, new CustomCollectionHandler(name, values, context, callbacks, tracerProvider, meterProvider, OrderedCollection, OrderedCollectionPage, filterPredicate, portable).fetchCollection(cursor), context, portable);
|
|
5211
|
+
}
|
|
5212
|
+
async function respondWithCustomCollection(request, handler, context, portable) {
|
|
4891
5213
|
try {
|
|
4892
|
-
const
|
|
5214
|
+
const jsonLd = await handler.toJsonLd();
|
|
5215
|
+
let response;
|
|
5216
|
+
if (portable == null) response = respondAsActivity(jsonLd);
|
|
5217
|
+
else if (await verifyPortableCollection(jsonLd, context, portable)) response = respondWithPortableCollection(request, jsonLd);
|
|
5218
|
+
else throw new PortableCollectionRefusedError();
|
|
4893
5219
|
handler.recordPendingCollectionMetrics("served", response);
|
|
4894
5220
|
return response;
|
|
4895
5221
|
} catch (e) {
|
|
@@ -4919,6 +5245,7 @@ var CustomCollectionHandler = class {
|
|
|
4919
5245
|
Collection;
|
|
4920
5246
|
CollectionPage;
|
|
4921
5247
|
filterPredicate;
|
|
5248
|
+
portable;
|
|
4922
5249
|
/**
|
|
4923
5250
|
* The tracer for telemetry.
|
|
4924
5251
|
* @type {Tracer}
|
|
@@ -4955,8 +5282,8 @@ var CustomCollectionHandler = class {
|
|
|
4955
5282
|
* @param CollectionPage The CollectionPage constructor.
|
|
4956
5283
|
* @param filterPredicate Optional filter predicate for items.
|
|
4957
5284
|
*/
|
|
4958
|
-
constructor(name$
|
|
4959
|
-
this.name = name$
|
|
5285
|
+
constructor(name$2, values, context, callbacks, tracerProvider = trace.getTracerProvider(), meterProvider, Collection, CollectionPage, filterPredicate, portable) {
|
|
5286
|
+
this.name = name$2;
|
|
4960
5287
|
this.values = values;
|
|
4961
5288
|
this.context = context;
|
|
4962
5289
|
this.callbacks = callbacks;
|
|
@@ -4965,9 +5292,14 @@ var CustomCollectionHandler = class {
|
|
|
4965
5292
|
this.Collection = Collection;
|
|
4966
5293
|
this.CollectionPage = CollectionPage;
|
|
4967
5294
|
this.filterPredicate = filterPredicate;
|
|
5295
|
+
this.portable = portable;
|
|
4968
5296
|
this.name = this.name.trim().replace(/\s+/g, "_");
|
|
4969
5297
|
this.#tracer = this.tracerProvider.getTracer(name, version);
|
|
4970
|
-
this.#id = new URL(this.context.url);
|
|
5298
|
+
if (portable == null) this.#id = new URL(this.context.url);
|
|
5299
|
+
else {
|
|
5300
|
+
const cursor = this.context.url.searchParams.get("cursor");
|
|
5301
|
+
this.#id = cursor == null ? new URL(portable.view) : appendCursorIfExists(portable.view, cursor);
|
|
5302
|
+
}
|
|
4971
5303
|
this.#dispatcher = callbacks.dispatcher.bind(callbacks);
|
|
4972
5304
|
}
|
|
4973
5305
|
/**
|
|
@@ -5018,6 +5350,7 @@ var CustomCollectionHandler = class {
|
|
|
5018
5350
|
this.recordPendingCollectionItemCount(true, items.length);
|
|
5019
5351
|
return {
|
|
5020
5352
|
id,
|
|
5353
|
+
attribution: this.portable?.attribution ?? null,
|
|
5021
5354
|
partOf,
|
|
5022
5355
|
items,
|
|
5023
5356
|
prev: this.appendToUrl(prevCursor),
|
|
@@ -5039,6 +5372,7 @@ var CustomCollectionHandler = class {
|
|
|
5039
5372
|
});
|
|
5040
5373
|
return {
|
|
5041
5374
|
id: this.#id,
|
|
5375
|
+
attribution: this.portable?.attribution ?? null,
|
|
5042
5376
|
first: this.appendToUrl(firstCursor),
|
|
5043
5377
|
last: this.appendToUrl(lastCursor),
|
|
5044
5378
|
totalItems
|
|
@@ -5055,6 +5389,7 @@ var CustomCollectionHandler = class {
|
|
|
5055
5389
|
this.recordPendingCollectionItemCount(false, items.length);
|
|
5056
5390
|
return {
|
|
5057
5391
|
id: this.#id,
|
|
5392
|
+
attribution: this.portable?.attribution ?? null,
|
|
5058
5393
|
totalItems,
|
|
5059
5394
|
items
|
|
5060
5395
|
};
|
|
@@ -5172,7 +5507,7 @@ var CustomCollectionHandler = class {
|
|
|
5172
5507
|
* @returns The URL with cursor appended, or null if cursor is null/undefined.
|
|
5173
5508
|
*/
|
|
5174
5509
|
appendToUrl(cursor) {
|
|
5175
|
-
return appendCursorIfExists(this.context.url, cursor);
|
|
5510
|
+
return appendCursorIfExists(this.portable?.view ?? this.context.url, cursor);
|
|
5176
5511
|
}
|
|
5177
5512
|
/**
|
|
5178
5513
|
* Gets the stored collection or collection page.
|
|
@@ -5249,6 +5584,12 @@ function exceptWrapper(handler) {
|
|
|
5249
5584
|
recordCollectionRequest(meterProvider, collectionAttributes(metricBase, "not_found", response));
|
|
5250
5585
|
return response;
|
|
5251
5586
|
}
|
|
5587
|
+
case PortableCollectionRefusedError: {
|
|
5588
|
+
const response = portableObjectInternalServerError(request);
|
|
5589
|
+
recordDeferredPendingCollectionMetrics(error, "error", response);
|
|
5590
|
+
recordCollectionRequest(meterProvider, collectionAttributes(metricBase, "error", response));
|
|
5591
|
+
return response;
|
|
5592
|
+
}
|
|
5252
5593
|
case UnauthorizedError: {
|
|
5253
5594
|
const response = await onUnauthorized(request);
|
|
5254
5595
|
recordDeferredPendingCollectionMetrics(error, "unauthorized", response);
|
|
@@ -5345,6 +5686,16 @@ var UnauthorizedError = class extends HandlerError {
|
|
|
5345
5686
|
}
|
|
5346
5687
|
};
|
|
5347
5688
|
/**
|
|
5689
|
+
* Error thrown when a portable collection is not served because it embeds
|
|
5690
|
+
* portable objects that do not satisfy the FEP-ef61 proof policy.
|
|
5691
|
+
* @since 2.4.0
|
|
5692
|
+
*/
|
|
5693
|
+
var PortableCollectionRefusedError = class extends HandlerError {
|
|
5694
|
+
constructor() {
|
|
5695
|
+
super("The portable collection embeds unverifiable portable objects.");
|
|
5696
|
+
}
|
|
5697
|
+
};
|
|
5698
|
+
/**
|
|
5348
5699
|
* Responds with the given object in JSON-LD format.
|
|
5349
5700
|
*
|
|
5350
5701
|
* @param object The object to respond with.
|
|
@@ -5714,6 +6065,100 @@ function serializeDataPointType(dataPointType) {
|
|
|
5714
6065
|
}
|
|
5715
6066
|
}
|
|
5716
6067
|
//#endregion
|
|
6068
|
+
//#region src/federation/portable-collection.ts
|
|
6069
|
+
/**
|
|
6070
|
+
* The query parameters of a gateway request for a portable collection that
|
|
6071
|
+
* do not select a view of the collection: the page cursor, and FEP-ef61
|
|
6072
|
+
* location hints (`@gateway`, and the legacy `gateways`).
|
|
6073
|
+
*/
|
|
6074
|
+
const NON_VIEW_PARAMETERS = /* @__PURE__ */ new Set([
|
|
6075
|
+
"cursor",
|
|
6076
|
+
"@gateway",
|
|
6077
|
+
"gateways"
|
|
6078
|
+
]);
|
|
6079
|
+
/**
|
|
6080
|
+
* Checks that a collection requested through the FEP-ef61 gateway endpoint
|
|
6081
|
+
* belongs to a portable actor under the requested DID, and determines
|
|
6082
|
+
* the collection's ID from the actor.
|
|
6083
|
+
*
|
|
6084
|
+
* Collection dispatchers return items, not documents with IDs, so unlike
|
|
6085
|
+
* object dispatchers, there is nothing else to tell whether the requested
|
|
6086
|
+
* DID controls the collection.
|
|
6087
|
+
*
|
|
6088
|
+
* @param actor The actor that owns the collection according to
|
|
6089
|
+
* the application, dispatched without the portable request,
|
|
6090
|
+
* or `null` if there is none.
|
|
6091
|
+
* @param options The requested DID, and either the requested collection's
|
|
6092
|
+
* canonical ID and the actor's property that has to refer to
|
|
6093
|
+
* it (for the built-in collections of an actor), or
|
|
6094
|
+
* the requested collection's portable ID (for a custom
|
|
6095
|
+
* collection, whose ID has the same form as the actor's).
|
|
6096
|
+
* @returns The resolution.
|
|
6097
|
+
*/
|
|
6098
|
+
function resolvePortableCollectionOwner(actor, options) {
|
|
6099
|
+
if (!isActor(actor)) return {
|
|
6100
|
+
status: "rejected",
|
|
6101
|
+
reason: "notFound"
|
|
6102
|
+
};
|
|
6103
|
+
const ownerId = actor.id;
|
|
6104
|
+
const did = ownerId == null || !isPortableId(ownerId) ? null : getPortableDid(ownerId);
|
|
6105
|
+
if (ownerId == null || did == null) return {
|
|
6106
|
+
status: "rejected",
|
|
6107
|
+
reason: "notPortable"
|
|
6108
|
+
};
|
|
6109
|
+
if (did !== options.authority) return {
|
|
6110
|
+
status: "rejected",
|
|
6111
|
+
reason: "authorityMismatch"
|
|
6112
|
+
};
|
|
6113
|
+
if ("portableId" in options) return {
|
|
6114
|
+
status: "accepted",
|
|
6115
|
+
ownerId,
|
|
6116
|
+
collectionId: getCustomCollectionId(ownerId, actor, options.portableId)
|
|
6117
|
+
};
|
|
6118
|
+
const property = options.property(actor);
|
|
6119
|
+
if (property == null || getCanonicalPortableId(property) !== options.canonicalId) return {
|
|
6120
|
+
status: "rejected",
|
|
6121
|
+
reason: "collectionMismatch"
|
|
6122
|
+
};
|
|
6123
|
+
const collectionId = new URL(property);
|
|
6124
|
+
collectionId.search = "";
|
|
6125
|
+
collectionId.hash = "";
|
|
6126
|
+
return {
|
|
6127
|
+
status: "accepted",
|
|
6128
|
+
ownerId,
|
|
6129
|
+
collectionId
|
|
6130
|
+
};
|
|
6131
|
+
}
|
|
6132
|
+
/**
|
|
6133
|
+
* Gets the ID of a custom portable collection in the same form as its owner's
|
|
6134
|
+
* ID: a portable ID if the owner has one, or otherwise a compatible identifier
|
|
6135
|
+
* on the owner's first gateway, as FEP-ef61 requires of publishers.
|
|
6136
|
+
*/
|
|
6137
|
+
function getCustomCollectionId(ownerId, owner, portableId) {
|
|
6138
|
+
if (isPortableUri(ownerId)) return portableId;
|
|
6139
|
+
const gateway = owner.gateways[0];
|
|
6140
|
+
if (gateway == null || !isGatewayUrl(gateway)) return portableId;
|
|
6141
|
+
try {
|
|
6142
|
+
return toCompatibleEf61Id(portableId, gateway);
|
|
6143
|
+
} catch (error) {
|
|
6144
|
+
if (error instanceof TypeError) return portableId;
|
|
6145
|
+
throw error;
|
|
6146
|
+
}
|
|
6147
|
+
}
|
|
6148
|
+
/**
|
|
6149
|
+
* Builds the URI that the pages of a portable collection are relative to:
|
|
6150
|
+
* the collection's ID with the query parameters of the requested view, e.g.,
|
|
6151
|
+
* a filter, but without the page cursor and location hints.
|
|
6152
|
+
* @param collectionId The ID of the collection.
|
|
6153
|
+
* @param requestUrl The URL of the gateway request.
|
|
6154
|
+
* @returns The view URI.
|
|
6155
|
+
*/
|
|
6156
|
+
function buildPortableCollectionView(collectionId, requestUrl) {
|
|
6157
|
+
const view = new URL(collectionId);
|
|
6158
|
+
for (const [name, value] of requestUrl.searchParams) if (!NON_VIEW_PARAMETERS.has(name)) view.searchParams.append(name, value);
|
|
6159
|
+
return view;
|
|
6160
|
+
}
|
|
6161
|
+
//#endregion
|
|
5717
6162
|
//#region src/federation/outgoing-proof.ts
|
|
5718
6163
|
function formatKeyIds(candidates) {
|
|
5719
6164
|
return candidates.map((c) => c.verificationMethod.href).join(", ");
|
|
@@ -8175,12 +8620,7 @@ var FederationImpl = class extends FederationBuilderImpl {
|
|
|
8175
8620
|
onNotFound
|
|
8176
8621
|
});
|
|
8177
8622
|
case "followers": {
|
|
8178
|
-
|
|
8179
|
-
if (baseUrl != null) try {
|
|
8180
|
-
baseUrl = `${new URL(baseUrl).origin}/`;
|
|
8181
|
-
} catch {
|
|
8182
|
-
baseUrl = null;
|
|
8183
|
-
}
|
|
8623
|
+
const baseUrl = getFollowersBaseUrl(url);
|
|
8184
8624
|
return await handleCollection(request, {
|
|
8185
8625
|
name: "followers",
|
|
8186
8626
|
identifier: route.values.identifier,
|
|
@@ -8191,7 +8631,7 @@ var FederationImpl = class extends FederationBuilderImpl {
|
|
|
8191
8631
|
},
|
|
8192
8632
|
context,
|
|
8193
8633
|
filter: baseUrl != null ? new URL(baseUrl) : void 0,
|
|
8194
|
-
filterPredicate: baseUrl != null ? (
|
|
8634
|
+
filterPredicate: baseUrl != null ? getFollowersFilterPredicate(baseUrl) : void 0,
|
|
8195
8635
|
collectionCallbacks: this.followersCallbacks,
|
|
8196
8636
|
tracerProvider: this.tracerProvider,
|
|
8197
8637
|
meterProvider: this._meterProvider,
|
|
@@ -8285,17 +8725,30 @@ var FederationImpl = class extends FederationBuilderImpl {
|
|
|
8285
8725
|
}
|
|
8286
8726
|
const route = this.router.route(portable.path);
|
|
8287
8727
|
const isActor = route != null && (route.name === "actor" || route.name.startsWith("actorAlias:"));
|
|
8288
|
-
|
|
8728
|
+
const collectionRoute = route == null ? void 0 : getCollectionMetricRoute(route.name.replace(/:.*$/, ""));
|
|
8729
|
+
if (route == null || !isActor && !route.name.startsWith("object:") && collectionRoute == null) {
|
|
8289
8730
|
metricState.endpoint = "not_found";
|
|
8290
8731
|
return await onNotFound(request);
|
|
8291
8732
|
}
|
|
8292
8733
|
metricState.routeTemplate = PORTABLE_GATEWAY_ROUTE_PREFIX + route.template;
|
|
8293
|
-
metricState.endpoint =
|
|
8734
|
+
metricState.endpoint = getEndpointCategory(route.name);
|
|
8294
8735
|
span.updateName(`${request.method} ${metricState.routeTemplate}`);
|
|
8295
8736
|
if (!acceptsJsonLd(request)) {
|
|
8296
8737
|
metricState.endpoint = "not_acceptable";
|
|
8297
|
-
|
|
8738
|
+
const response = await onNotAcceptable(request);
|
|
8739
|
+
if (collectionRoute != null) recordCollectionRequest(this._meterProvider, {
|
|
8740
|
+
...collectionRoute,
|
|
8741
|
+
page: new URL(request.url).searchParams.get("cursor") != null,
|
|
8742
|
+
result: "not_acceptable",
|
|
8743
|
+
statusCode: response.status
|
|
8744
|
+
});
|
|
8745
|
+
return response;
|
|
8298
8746
|
}
|
|
8747
|
+
if (collectionRoute != null) return await this.#fetchPortableCollection(request, portable, route, {
|
|
8748
|
+
onNotFound,
|
|
8749
|
+
onUnauthorized,
|
|
8750
|
+
contextData
|
|
8751
|
+
});
|
|
8299
8752
|
if (isActor) {
|
|
8300
8753
|
const identifier = route.name.startsWith("actorAlias:") ? route.name.substring(11) : route.values.identifier;
|
|
8301
8754
|
const context = this.#createContext(request, contextData, {
|
|
@@ -8334,6 +8787,108 @@ var FederationImpl = class extends FederationBuilderImpl {
|
|
|
8334
8787
|
onNotFound
|
|
8335
8788
|
});
|
|
8336
8789
|
}
|
|
8790
|
+
async #fetchPortableCollection(request, portable, route, { onNotFound, onUnauthorized, contextData }) {
|
|
8791
|
+
const logger = getLogger([
|
|
8792
|
+
"fedify",
|
|
8793
|
+
"federation",
|
|
8794
|
+
"collection"
|
|
8795
|
+
]);
|
|
8796
|
+
const url = new URL(request.url);
|
|
8797
|
+
const { authority } = portable.portableRequest;
|
|
8798
|
+
const ownerContext = this.#createContext(request, contextData);
|
|
8799
|
+
const context = this.#createContext(request, contextData, { portableRequest: portable.portableRequest });
|
|
8800
|
+
const reject = async (reason, identifier) => {
|
|
8801
|
+
logger.debug("Not serving the portable collection {collection} of the actor {identifier}: {reason}.", {
|
|
8802
|
+
collection: portable.canonicalId,
|
|
8803
|
+
identifier,
|
|
8804
|
+
reason
|
|
8805
|
+
});
|
|
8806
|
+
return await onNotFound(request);
|
|
8807
|
+
};
|
|
8808
|
+
const routeName = route.name.replace(/:.*$/, "");
|
|
8809
|
+
if (routeName === "collection" || routeName === "orderedCollection") {
|
|
8810
|
+
const name = route.name.replace(/^(?:ordered)?[cC]ollection:/, "");
|
|
8811
|
+
const callbacks = this.collectionCallbacks[name];
|
|
8812
|
+
if (callbacks == null) return await onNotFound(request);
|
|
8813
|
+
const mapper = callbacks.portableOwnerMapper;
|
|
8814
|
+
if (mapper == null) return await reject("noPortableOwnerMapper");
|
|
8815
|
+
const identifier = await mapper(ownerContext, route.values);
|
|
8816
|
+
if (identifier == null) return await reject("notPortable");
|
|
8817
|
+
const resolution = resolvePortableCollectionOwner(await this.actorCallbacks?.dispatcher?.(ownerContext, identifier), {
|
|
8818
|
+
authority,
|
|
8819
|
+
portableId: portable.portableRequest.id
|
|
8820
|
+
});
|
|
8821
|
+
if (resolution.status === "rejected") return await reject(resolution.reason, identifier);
|
|
8822
|
+
const view = buildPortableCollectionView(resolution.collectionId, url);
|
|
8823
|
+
return await (routeName === "collection" ? handleCustomCollection : handleOrderedCollection)(request, {
|
|
8824
|
+
name,
|
|
8825
|
+
context,
|
|
8826
|
+
values: route.values,
|
|
8827
|
+
collectionCallbacks: callbacks,
|
|
8828
|
+
portable: {
|
|
8829
|
+
id: view,
|
|
8830
|
+
view,
|
|
8831
|
+
attribution: resolution.ownerId
|
|
8832
|
+
},
|
|
8833
|
+
tracerProvider: this.tracerProvider,
|
|
8834
|
+
meterProvider: this._meterProvider,
|
|
8835
|
+
onUnauthorized,
|
|
8836
|
+
onNotFound
|
|
8837
|
+
});
|
|
8838
|
+
}
|
|
8839
|
+
const identifier = route.values.identifier;
|
|
8840
|
+
const serve = async (name, callbacks, property, filter) => {
|
|
8841
|
+
if (callbacks == null) return await onNotFound(request);
|
|
8842
|
+
const resolution = resolvePortableCollectionOwner(await this.actorCallbacks?.dispatcher?.(ownerContext, identifier), {
|
|
8843
|
+
authority,
|
|
8844
|
+
canonicalId: portable.canonicalId,
|
|
8845
|
+
property
|
|
8846
|
+
});
|
|
8847
|
+
if (resolution.status === "rejected") return await reject(resolution.reason, identifier);
|
|
8848
|
+
const id = new URL(resolution.collectionId);
|
|
8849
|
+
if (filter != null) id.searchParams.set(...filter.parameter);
|
|
8850
|
+
const view = buildPortableCollectionView(resolution.collectionId, url);
|
|
8851
|
+
if (routeName === "followers") {
|
|
8852
|
+
view.searchParams.delete("base-url");
|
|
8853
|
+
if (filter != null) view.searchParams.set(...filter.parameter);
|
|
8854
|
+
}
|
|
8855
|
+
return await handleCollection(request, {
|
|
8856
|
+
name,
|
|
8857
|
+
identifier,
|
|
8858
|
+
uriGetter: () => id,
|
|
8859
|
+
context,
|
|
8860
|
+
filter: filter?.value,
|
|
8861
|
+
filterPredicate: filter?.predicate,
|
|
8862
|
+
collectionCallbacks: callbacks,
|
|
8863
|
+
portable: {
|
|
8864
|
+
id,
|
|
8865
|
+
view,
|
|
8866
|
+
attribution: resolution.ownerId
|
|
8867
|
+
},
|
|
8868
|
+
tracerProvider: this.tracerProvider,
|
|
8869
|
+
meterProvider: this._meterProvider,
|
|
8870
|
+
onUnauthorized,
|
|
8871
|
+
onNotFound
|
|
8872
|
+
});
|
|
8873
|
+
};
|
|
8874
|
+
switch (routeName) {
|
|
8875
|
+
case "outbox": return await serve("outbox", this.outboxCallbacks, (a) => a.outboxId);
|
|
8876
|
+
case "inbox": return await serve("inbox", this.inboxCallbacks, (a) => a.inboxId);
|
|
8877
|
+
case "following": return await serve("following", this.followingCallbacks, (a) => a.followingId);
|
|
8878
|
+
case "followers": {
|
|
8879
|
+
const baseUrl = getFollowersBaseUrl(url);
|
|
8880
|
+
return await serve("followers", this.followersCallbacks, (a) => a.followersId, baseUrl == null ? void 0 : {
|
|
8881
|
+
value: new URL(baseUrl),
|
|
8882
|
+
predicate: getFollowersFilterPredicate(baseUrl),
|
|
8883
|
+
parameter: ["base-url", baseUrl]
|
|
8884
|
+
});
|
|
8885
|
+
}
|
|
8886
|
+
case "liked": return await serve("liked", this.likedCallbacks, (a) => a.likedId);
|
|
8887
|
+
case "featured": return await serve("featured", this.featuredCallbacks, (a) => a.featuredId);
|
|
8888
|
+
case "featuredTags": return await serve("featured tags", this.featuredTagsCallbacks, (a) => a.featuredTagsId);
|
|
8889
|
+
}
|
|
8890
|
+
return await onNotFound(request);
|
|
8891
|
+
}
|
|
8337
8892
|
async #handleInbox(request, { recipient, context, contextData, onNotFound, portableInbox }) {
|
|
8338
8893
|
if (!this.manuallyStartQueue) this._startQueueInternal(contextData);
|
|
8339
8894
|
const inboxContextFactory = context.toInboxContext.bind(context);
|
|
@@ -8520,6 +9075,41 @@ var FederationImpl = class extends FederationBuilderImpl {
|
|
|
8520
9075
|
};
|
|
8521
9076
|
const PORTABLE_GATEWAY_ROUTE_PREFIX = "/.well-known/apgateway/{did}";
|
|
8522
9077
|
/**
|
|
9078
|
+
* Gets the origin that a followers collection request filters followers by,
|
|
9079
|
+
* i.e., its normalized `base-url` query parameter (FEP-8fcf).
|
|
9080
|
+
* @returns The origin with a trailing slash, or `null` if the parameter is
|
|
9081
|
+
* missing or invalid.
|
|
9082
|
+
*/
|
|
9083
|
+
function getFollowersBaseUrl(url) {
|
|
9084
|
+
const baseUrl = url.searchParams.get("base-url");
|
|
9085
|
+
if (baseUrl == null) return null;
|
|
9086
|
+
try {
|
|
9087
|
+
return `${new URL(baseUrl).origin}/`;
|
|
9088
|
+
} catch {
|
|
9089
|
+
return null;
|
|
9090
|
+
}
|
|
9091
|
+
}
|
|
9092
|
+
function getFollowersFilterPredicate(baseUrl) {
|
|
9093
|
+
return (i) => (i instanceof URL ? i.href : i.id?.href ?? "").startsWith(baseUrl);
|
|
9094
|
+
}
|
|
9095
|
+
const ACTOR_COLLECTION_ROUTE_ERRORS = {
|
|
9096
|
+
outbox: "No outbox dispatcher registered.",
|
|
9097
|
+
following: "No following collection path registered.",
|
|
9098
|
+
followers: "No followers collection path registered.",
|
|
9099
|
+
liked: "No liked collection path registered.",
|
|
9100
|
+
featured: "No featured collection path registered.",
|
|
9101
|
+
featuredTags: "No featured tags collection path registered."
|
|
9102
|
+
};
|
|
9103
|
+
/**
|
|
9104
|
+
* Checks that the authority of a portable ID to build is given, either
|
|
9105
|
+
* explicitly or by the FEP-ef61 gateway request being handled.
|
|
9106
|
+
* @throws {TypeError} If the authority is missing.
|
|
9107
|
+
*/
|
|
9108
|
+
function requirePortableAuthority(authority) {
|
|
9109
|
+
if (authority == null) throw new TypeError("The authority of a portable ID is required outside an FEP-ef61 gateway request.");
|
|
9110
|
+
return authority;
|
|
9111
|
+
}
|
|
9112
|
+
/**
|
|
8523
9113
|
* Removes the body of a response to a `HEAD` request.
|
|
8524
9114
|
* @param request The request.
|
|
8525
9115
|
* @param response The response to the request.
|
|
@@ -8698,8 +9288,7 @@ var ContextImpl = class ContextImpl {
|
|
|
8698
9288
|
}
|
|
8699
9289
|
getPortableActorUri(identifier, authority) {
|
|
8700
9290
|
const path = this.#getActorPath(identifier);
|
|
8701
|
-
|
|
8702
|
-
return buildPortableUri(authority, path);
|
|
9291
|
+
return buildPortableUri(requirePortableAuthority(authority), path);
|
|
8703
9292
|
}
|
|
8704
9293
|
#getActorPath(identifier) {
|
|
8705
9294
|
const path = this.federation.router.build(`actorAlias:${identifier}`, {}) ?? this.federation.router.build("actor", { identifier });
|
|
@@ -8711,8 +9300,7 @@ var ContextImpl = class ContextImpl {
|
|
|
8711
9300
|
}
|
|
8712
9301
|
getPortableObjectUri(cls, values, authority) {
|
|
8713
9302
|
const path = this.#getObjectPath(cls, values);
|
|
8714
|
-
|
|
8715
|
-
return buildPortableUri(authority, path);
|
|
9303
|
+
return buildPortableUri(requirePortableAuthority(authority), path);
|
|
8716
9304
|
}
|
|
8717
9305
|
#getObjectPath(cls, values) {
|
|
8718
9306
|
const callbacks = this.federation.objectCallbacks[cls.typeId.href];
|
|
@@ -8722,10 +9310,17 @@ var ContextImpl = class ContextImpl {
|
|
|
8722
9310
|
if (path == null) throw new RouterError("No object dispatcher registered.");
|
|
8723
9311
|
return path;
|
|
8724
9312
|
}
|
|
9313
|
+
#getActorCollectionPath(route, identifier) {
|
|
9314
|
+
const path = this.federation.router.build(route, { identifier });
|
|
9315
|
+
if (path == null) throw new RouterError(ACTOR_COLLECTION_ROUTE_ERRORS[route]);
|
|
9316
|
+
return path;
|
|
9317
|
+
}
|
|
8725
9318
|
getOutboxUri(identifier) {
|
|
8726
|
-
|
|
8727
|
-
|
|
8728
|
-
|
|
9319
|
+
return new URL(this.#getActorCollectionPath("outbox", identifier), this.canonicalOrigin);
|
|
9320
|
+
}
|
|
9321
|
+
getPortableOutboxUri(identifier, authority) {
|
|
9322
|
+
const path = this.#getActorCollectionPath("outbox", identifier);
|
|
9323
|
+
return buildPortableUri(requirePortableAuthority(authority), path);
|
|
8729
9324
|
}
|
|
8730
9325
|
getMediaUploaderUri(identifier) {
|
|
8731
9326
|
const path = this.federation.router.build("mediaUploader", { identifier });
|
|
@@ -8745,39 +9340,53 @@ var ContextImpl = class ContextImpl {
|
|
|
8745
9340
|
getPortableInboxUri(identifier, authority) {
|
|
8746
9341
|
const path = this.federation.router.build("inbox", { identifier });
|
|
8747
9342
|
if (path == null) throw new RouterError("No inbox path registered.");
|
|
8748
|
-
|
|
8749
|
-
return buildPortableUri(authority, path);
|
|
9343
|
+
return buildPortableUri(requirePortableAuthority(authority), path);
|
|
8750
9344
|
}
|
|
8751
9345
|
getFollowingUri(identifier) {
|
|
8752
|
-
|
|
8753
|
-
|
|
8754
|
-
|
|
9346
|
+
return new URL(this.#getActorCollectionPath("following", identifier), this.canonicalOrigin);
|
|
9347
|
+
}
|
|
9348
|
+
getPortableFollowingUri(identifier, authority) {
|
|
9349
|
+
const path = this.#getActorCollectionPath("following", identifier);
|
|
9350
|
+
return buildPortableUri(requirePortableAuthority(authority), path);
|
|
8755
9351
|
}
|
|
8756
9352
|
getFollowersUri(identifier) {
|
|
8757
|
-
|
|
8758
|
-
|
|
8759
|
-
|
|
9353
|
+
return new URL(this.#getActorCollectionPath("followers", identifier), this.canonicalOrigin);
|
|
9354
|
+
}
|
|
9355
|
+
getPortableFollowersUri(identifier, authority) {
|
|
9356
|
+
const path = this.#getActorCollectionPath("followers", identifier);
|
|
9357
|
+
return buildPortableUri(requirePortableAuthority(authority), path);
|
|
8760
9358
|
}
|
|
8761
9359
|
getLikedUri(identifier) {
|
|
8762
|
-
|
|
8763
|
-
|
|
8764
|
-
|
|
9360
|
+
return new URL(this.#getActorCollectionPath("liked", identifier), this.canonicalOrigin);
|
|
9361
|
+
}
|
|
9362
|
+
getPortableLikedUri(identifier, authority) {
|
|
9363
|
+
const path = this.#getActorCollectionPath("liked", identifier);
|
|
9364
|
+
return buildPortableUri(requirePortableAuthority(authority), path);
|
|
8765
9365
|
}
|
|
8766
9366
|
getFeaturedUri(identifier) {
|
|
8767
|
-
|
|
8768
|
-
|
|
8769
|
-
|
|
9367
|
+
return new URL(this.#getActorCollectionPath("featured", identifier), this.canonicalOrigin);
|
|
9368
|
+
}
|
|
9369
|
+
getPortableFeaturedUri(identifier, authority) {
|
|
9370
|
+
const path = this.#getActorCollectionPath("featured", identifier);
|
|
9371
|
+
return buildPortableUri(requirePortableAuthority(authority), path);
|
|
8770
9372
|
}
|
|
8771
9373
|
getFeaturedTagsUri(identifier) {
|
|
8772
|
-
|
|
8773
|
-
|
|
8774
|
-
|
|
9374
|
+
return new URL(this.#getActorCollectionPath("featuredTags", identifier), this.canonicalOrigin);
|
|
9375
|
+
}
|
|
9376
|
+
getPortableFeaturedTagsUri(identifier, authority) {
|
|
9377
|
+
const path = this.#getActorCollectionPath("featuredTags", identifier);
|
|
9378
|
+
return buildPortableUri(requirePortableAuthority(authority), path);
|
|
8775
9379
|
}
|
|
8776
9380
|
getCollectionUri(name, values) {
|
|
8777
9381
|
const path = this.federation.getCollectionPath(name, values);
|
|
8778
9382
|
if (path === null) throw new RouterError(`No collection dispatcher registered for "${String(name)}".`);
|
|
8779
9383
|
return new URL(path, this.canonicalOrigin);
|
|
8780
9384
|
}
|
|
9385
|
+
getPortableCollectionUri(name, values, authority) {
|
|
9386
|
+
const path = this.federation.getCollectionPath(name, values);
|
|
9387
|
+
if (path === null) throw new RouterError(`No collection dispatcher registered for "${String(name)}".`);
|
|
9388
|
+
return buildPortableUri(requirePortableAuthority(authority), path);
|
|
9389
|
+
}
|
|
8781
9390
|
parseUri(uri) {
|
|
8782
9391
|
if (uri == null) return null;
|
|
8783
9392
|
if (uri.origin !== this.origin && uri.origin !== this.canonicalOrigin) return null;
|
|
@@ -9357,6 +9966,27 @@ var RequestContextImpl = class RequestContextImpl extends ContextImpl {
|
|
|
9357
9966
|
getPortableInboxUri(identifier, authority) {
|
|
9358
9967
|
return super.getPortableInboxUri(identifier, authority ?? this.portableRequest?.authority);
|
|
9359
9968
|
}
|
|
9969
|
+
getPortableOutboxUri(identifier, authority) {
|
|
9970
|
+
return super.getPortableOutboxUri(identifier, authority ?? this.portableRequest?.authority);
|
|
9971
|
+
}
|
|
9972
|
+
getPortableFollowingUri(identifier, authority) {
|
|
9973
|
+
return super.getPortableFollowingUri(identifier, authority ?? this.portableRequest?.authority);
|
|
9974
|
+
}
|
|
9975
|
+
getPortableFollowersUri(identifier, authority) {
|
|
9976
|
+
return super.getPortableFollowersUri(identifier, authority ?? this.portableRequest?.authority);
|
|
9977
|
+
}
|
|
9978
|
+
getPortableLikedUri(identifier, authority) {
|
|
9979
|
+
return super.getPortableLikedUri(identifier, authority ?? this.portableRequest?.authority);
|
|
9980
|
+
}
|
|
9981
|
+
getPortableFeaturedUri(identifier, authority) {
|
|
9982
|
+
return super.getPortableFeaturedUri(identifier, authority ?? this.portableRequest?.authority);
|
|
9983
|
+
}
|
|
9984
|
+
getPortableFeaturedTagsUri(identifier, authority) {
|
|
9985
|
+
return super.getPortableFeaturedTagsUri(identifier, authority ?? this.portableRequest?.authority);
|
|
9986
|
+
}
|
|
9987
|
+
getPortableCollectionUri(name, values, authority) {
|
|
9988
|
+
return super.getPortableCollectionUri(name, values, authority ?? this.portableRequest?.authority);
|
|
9989
|
+
}
|
|
9360
9990
|
async getActor(identifier, options) {
|
|
9361
9991
|
if (this.federation.actorCallbacks == null || this.federation.actorCallbacks.dispatcher == null) throw new Error("No actor dispatcher registered.");
|
|
9362
9992
|
if (this.#invokedFromActorDispatcher != null) getLogger([
|