@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,10 +1,10 @@
|
|
|
1
1
|
const { URLPattern } = require("urlpattern-polyfill");
|
|
2
2
|
const require_rolldown_runtime = require("./rolldown-runtime-Dmm3t92W.cjs");
|
|
3
3
|
const require_transformers = require("./transformers-BalCrSsr.cjs");
|
|
4
|
-
const require_http = require("./http-
|
|
5
|
-
const require_types = require("./types-
|
|
6
|
-
const require_portable_collection = require("./portable-collection-
|
|
7
|
-
const require_kv_cache = require("./kv-cache-
|
|
4
|
+
const require_http = require("./http-BObQoB0j.cjs");
|
|
5
|
+
const require_types = require("./types-poIao40f.cjs");
|
|
6
|
+
const require_portable_collection = require("./portable-collection-B8ojoQ50.cjs");
|
|
7
|
+
const require_kv_cache = require("./kv-cache-D2fnB8qu.cjs");
|
|
8
8
|
let _logtape_logtape = require("@logtape/logtape");
|
|
9
9
|
let _fedify_uri_template = require("@fedify/uri-template");
|
|
10
10
|
let _fedify_vocab = require("@fedify/vocab");
|
|
@@ -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 _fedify_vocab.Tombstone) return actor;
|
|
213
213
|
if (actor.id != null && (0, _fedify_vocab_runtime_internal_portable_dereference.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 : require_http.getPortableDid(actor.id);
|
|
733
|
+
if (did == null) return;
|
|
734
|
+
const logger = (0, _logtape_logtape.getLogger)([
|
|
735
|
+
"fedify",
|
|
736
|
+
"federation",
|
|
737
|
+
"actor"
|
|
738
|
+
]);
|
|
739
|
+
const check = (registered, property, value, helper, build) => {
|
|
740
|
+
if (!registered || value == null || !require_http.isPortableId(value)) return;
|
|
741
|
+
const actual = require_http.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 _fedify_uri_template.RouterError) return;
|
|
754
|
+
throw error;
|
|
755
|
+
}
|
|
756
|
+
if (actual === require_http.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.
|
|
@@ -2068,6 +2121,218 @@ function isSingleStringValue(values, datatype) {
|
|
|
2068
2121
|
const keys = Object.keys(value);
|
|
2069
2122
|
return keys.length === 1 || datatype != null && keys.length === 2 && value["@type"] === datatype;
|
|
2070
2123
|
}
|
|
2124
|
+
/**
|
|
2125
|
+
* FEP-2277 core types that FEP-ef61 does not require to carry proofs.
|
|
2126
|
+
*/
|
|
2127
|
+
const UNSECURED_CORE_TYPES = /* @__PURE__ */ new Set([
|
|
2128
|
+
"collection",
|
|
2129
|
+
"link",
|
|
2130
|
+
"verificationMethod",
|
|
2131
|
+
"publicKey"
|
|
2132
|
+
]);
|
|
2133
|
+
/**
|
|
2134
|
+
* Applies the FEP-ef61 proof policy, under the map-local compound-proof
|
|
2135
|
+
* profile, to every portable object embedded in a document that a gateway is
|
|
2136
|
+
* about to serve, e.g., the items of a portable collection page. The root
|
|
2137
|
+
* map itself is not checked, as the caller built it.
|
|
2138
|
+
*
|
|
2139
|
+
* Each map below the root whose direct `id` or `@id` is a portable ID or
|
|
2140
|
+
* a compatible identifier is checked independently of the others:
|
|
2141
|
+
*
|
|
2142
|
+
* - A map with a direct `proof` is a separately secured document: the proof
|
|
2143
|
+
* has to be a single map, the map has to carry its own `@context`, and
|
|
2144
|
+
* the proof has to verify over the map exactly as it is served, with only
|
|
2145
|
+
* its own proof removed, and be made with a key of the DID in the map's
|
|
2146
|
+
* ID, as the map-local compound-proof profile requires.
|
|
2147
|
+
* - A map without a proof has to be a collection, a link, or a key, which
|
|
2148
|
+
* FEP-ef61 does not require to be secured. It is classified under its
|
|
2149
|
+
* effective context, i.e., the `@context` values of its ancestors and
|
|
2150
|
+
* itself, which therefore may only use the simple, preloaded contexts
|
|
2151
|
+
* that the compound-proof policy accepts.
|
|
2152
|
+
*
|
|
2153
|
+
* Maps that merely refer to a portable object, i.e., have no member other
|
|
2154
|
+
* than `id` or `@id`, and maps with non-portable IDs, are neither checked nor
|
|
2155
|
+
* verified. Every `@context` in the document, however, has to be simple, and
|
|
2156
|
+
* every portable object that the expanded document describes has to be
|
|
2157
|
+
* a map found this way, so that no context can hide a portable object from
|
|
2158
|
+
* this check, e.g., by aliasing `@id` or redefining `proof`. The input must be a finite, already-materialized JSON tree, such
|
|
2159
|
+
* as the compact JSON-LD document that is about to be served.
|
|
2160
|
+
*
|
|
2161
|
+
* @param json The document to check.
|
|
2162
|
+
* @param options The options for verifying the proofs. Proofs whose
|
|
2163
|
+
* verification methods are not `did:key` DID URLs may make
|
|
2164
|
+
* the document loader fetch DID documents.
|
|
2165
|
+
* @returns The result, which names the first refused map, if any.
|
|
2166
|
+
* @internal
|
|
2167
|
+
*/
|
|
2168
|
+
async function verifyServedPortableObjects(json, options = {}) {
|
|
2169
|
+
json = JSON.parse(JSON.stringify(json));
|
|
2170
|
+
const collected = collectServedPortableMaps(json);
|
|
2171
|
+
if (!("maps" in collected)) return collected;
|
|
2172
|
+
const maps = collected.maps;
|
|
2173
|
+
const discovered = /* @__PURE__ */ new Map();
|
|
2174
|
+
const ids = maps.map((candidate) => candidate.id);
|
|
2175
|
+
const rootId = isJsonMap(json) ? [json.id, json["@id"]].find(isPortableIdValue) : void 0;
|
|
2176
|
+
if (rootId != null) ids.push(rootId);
|
|
2177
|
+
for (const id of ids) discovered.set(id, (discovered.get(id) ?? 0) + 1);
|
|
2178
|
+
let hidden;
|
|
2179
|
+
try {
|
|
2180
|
+
hidden = findHiddenPortableObject(await _fedify_vocab_runtime_jsonld.default.expand(json, { documentLoader: require_http.preloadedOnlyDocumentLoader }), discovered);
|
|
2181
|
+
} catch {
|
|
2182
|
+
return {
|
|
2183
|
+
verified: false,
|
|
2184
|
+
path: "",
|
|
2185
|
+
id: "",
|
|
2186
|
+
reason: "unsupportedContext"
|
|
2187
|
+
};
|
|
2188
|
+
}
|
|
2189
|
+
if (hidden != null) return {
|
|
2190
|
+
verified: false,
|
|
2191
|
+
path: "",
|
|
2192
|
+
id: hidden,
|
|
2193
|
+
reason: "hiddenPortableObject"
|
|
2194
|
+
};
|
|
2195
|
+
for (const candidate of maps) {
|
|
2196
|
+
const reason = await checkServedPortableMap(candidate, options);
|
|
2197
|
+
if (reason != null) return {
|
|
2198
|
+
verified: false,
|
|
2199
|
+
path: candidate.path,
|
|
2200
|
+
id: candidate.id,
|
|
2201
|
+
reason
|
|
2202
|
+
};
|
|
2203
|
+
}
|
|
2204
|
+
return { verified: true };
|
|
2205
|
+
}
|
|
2206
|
+
/**
|
|
2207
|
+
* Finds a portable object that an expanded JSON-LD document describes, i.e.,
|
|
2208
|
+
* a node with a portable `@id` and other members, more times than the given
|
|
2209
|
+
* numbers of maps found for its ID.
|
|
2210
|
+
*/
|
|
2211
|
+
function findHiddenPortableObject(expanded, discovered) {
|
|
2212
|
+
const remaining = new Map(discovered);
|
|
2213
|
+
const pending = [expanded];
|
|
2214
|
+
while (pending.length > 0) {
|
|
2215
|
+
const value = pending.pop();
|
|
2216
|
+
if (Array.isArray(value)) {
|
|
2217
|
+
pending.push(...value);
|
|
2218
|
+
continue;
|
|
2219
|
+
}
|
|
2220
|
+
if (!isJsonMap(value)) continue;
|
|
2221
|
+
const id = value["@id"];
|
|
2222
|
+
if (isPortableIdValue(id) && Object.keys(value).some((key) => key !== "@id")) {
|
|
2223
|
+
const count = remaining.get(id) ?? 0;
|
|
2224
|
+
if (count < 1) return id;
|
|
2225
|
+
remaining.set(id, count - 1);
|
|
2226
|
+
}
|
|
2227
|
+
pending.push(...Object.values(value));
|
|
2228
|
+
}
|
|
2229
|
+
}
|
|
2230
|
+
/**
|
|
2231
|
+
* Checks whether a context uses only preloaded remote contexts and simple
|
|
2232
|
+
* term definitions, so that the context in effect for a map can be told from
|
|
2233
|
+
* the contexts of its ancestors.
|
|
2234
|
+
*/
|
|
2235
|
+
function isCheckableContext(context) {
|
|
2236
|
+
return (Array.isArray(context) ? context : [context]).every((value) => typeof value === "string" ? Object.hasOwn(_fedify_vocab_runtime.preloadedContexts, value) : !Array.isArray(value) && isSimpleContext(value));
|
|
2237
|
+
}
|
|
2238
|
+
/**
|
|
2239
|
+
* Checks whether every remote context that a context refers to is preloaded,
|
|
2240
|
+
* so that all of its definitions are known.
|
|
2241
|
+
*/
|
|
2242
|
+
function hasOnlyPreloadedRemoteContexts(context) {
|
|
2243
|
+
return (Array.isArray(context) ? context : [context]).every((value) => typeof value === "string" ? Object.hasOwn(_fedify_vocab_runtime.preloadedContexts, value) : !Array.isArray(value));
|
|
2244
|
+
}
|
|
2245
|
+
/**
|
|
2246
|
+
* Checks whether a map or any map below it, outside proof and context values,
|
|
2247
|
+
* has a portable `id` or `@id` and other members.
|
|
2248
|
+
*/
|
|
2249
|
+
function hasLiteralPortableObject(json) {
|
|
2250
|
+
return findInJsonMaps(json, (map) => [map.id, map["@id"]].some(isPortableIdValue) && Object.keys(map).some((key) => key !== "id" && key !== "@id") ? true : void 0) ?? false;
|
|
2251
|
+
}
|
|
2252
|
+
function collectServedPortableMaps(json) {
|
|
2253
|
+
const maps = [];
|
|
2254
|
+
const seen = /* @__PURE__ */ new Set();
|
|
2255
|
+
const pending = [{
|
|
2256
|
+
value: json,
|
|
2257
|
+
path: "",
|
|
2258
|
+
depth: 0,
|
|
2259
|
+
contexts: []
|
|
2260
|
+
}];
|
|
2261
|
+
while (pending.length > 0) {
|
|
2262
|
+
const { value, path, depth, contexts } = pending.pop();
|
|
2263
|
+
if (typeof value !== "object" || value == null || seen.has(value)) continue;
|
|
2264
|
+
seen.add(value);
|
|
2265
|
+
if (Array.isArray(value)) {
|
|
2266
|
+
for (let index = value.length - 1; index >= 0; index--) pending.push({
|
|
2267
|
+
value: value[index],
|
|
2268
|
+
path: childPath(path, String(index)),
|
|
2269
|
+
depth: depth + 1,
|
|
2270
|
+
contexts
|
|
2271
|
+
});
|
|
2272
|
+
continue;
|
|
2273
|
+
}
|
|
2274
|
+
const map = value;
|
|
2275
|
+
const hasContext = Object.hasOwn(map, "@context");
|
|
2276
|
+
if (hasContext && !isCheckableContext(map["@context"])) {
|
|
2277
|
+
if (hasOnlyPreloadedRemoteContexts(map["@context"]) && !hasLiteralPortableObject(map)) continue;
|
|
2278
|
+
const id = [map.id, map["@id"]].find((v) => typeof v === "string");
|
|
2279
|
+
return {
|
|
2280
|
+
verified: false,
|
|
2281
|
+
path,
|
|
2282
|
+
id: typeof id === "string" ? id : "",
|
|
2283
|
+
reason: "unsupportedContext"
|
|
2284
|
+
};
|
|
2285
|
+
}
|
|
2286
|
+
const mapContexts = hasContext ? [...contexts, map["@context"]] : contexts;
|
|
2287
|
+
const id = [map.id, map["@id"]].find(isPortableIdValue);
|
|
2288
|
+
if (depth > 0 && id != null && Object.keys(map).some((key) => key !== "id" && key !== "@id")) maps.push({
|
|
2289
|
+
path,
|
|
2290
|
+
id,
|
|
2291
|
+
map,
|
|
2292
|
+
contexts: mapContexts
|
|
2293
|
+
});
|
|
2294
|
+
const keys = Object.keys(map);
|
|
2295
|
+
for (let index = keys.length - 1; index >= 0; index--) {
|
|
2296
|
+
const key = keys[index];
|
|
2297
|
+
if (key === "proof" || key === "@context") continue;
|
|
2298
|
+
pending.push({
|
|
2299
|
+
value: map[key],
|
|
2300
|
+
path: childPath(path, key),
|
|
2301
|
+
depth: depth + 1,
|
|
2302
|
+
contexts: mapContexts
|
|
2303
|
+
});
|
|
2304
|
+
}
|
|
2305
|
+
}
|
|
2306
|
+
maps.sort((left, right) => comparePaths(left.path, right.path));
|
|
2307
|
+
return { maps };
|
|
2308
|
+
}
|
|
2309
|
+
async function checkServedPortableMap({ map, contexts }, options) {
|
|
2310
|
+
if (Object.hasOwn(map, "proof")) {
|
|
2311
|
+
if (!isJsonMap(map.proof)) return "unsupportedProofShape";
|
|
2312
|
+
if (!Object.hasOwn(map, "@context")) return "missingContext";
|
|
2313
|
+
try {
|
|
2314
|
+
const policy = await require_http.verifyPortableObjectProofPolicy(map, await require_http.verifyMapLocalProof(map, options), options);
|
|
2315
|
+
return policy.verified ? null : policy.reason.type;
|
|
2316
|
+
} catch (error) {
|
|
2317
|
+
if (error instanceof TypeError) return "invalidPortableObject";
|
|
2318
|
+
throw error;
|
|
2319
|
+
}
|
|
2320
|
+
}
|
|
2321
|
+
const flattened = contexts.flatMap((context) => Array.isArray(context) ? context : [context]);
|
|
2322
|
+
if (flattened.length < 1 || !isSimpleContext(flattened)) return "unsupportedContext";
|
|
2323
|
+
let expanded;
|
|
2324
|
+
try {
|
|
2325
|
+
expanded = await _fedify_vocab_runtime_jsonld.default.expand({
|
|
2326
|
+
...map,
|
|
2327
|
+
"@context": flattened
|
|
2328
|
+
}, { documentLoader: require_http.preloadedOnlyDocumentLoader });
|
|
2329
|
+
} catch {
|
|
2330
|
+
return "unsupportedContext";
|
|
2331
|
+
}
|
|
2332
|
+
if (expanded.length !== 1 || !isJsonMap(expanded[0])) return "invalidPortableObject";
|
|
2333
|
+
const type = require_http.classifyFep2277CoreType(expanded[0]);
|
|
2334
|
+
return UNSECURED_CORE_TYPES.has(type) ? null : "missingProof";
|
|
2335
|
+
}
|
|
2071
2336
|
//#endregion
|
|
2072
2337
|
//#region src/federation/inbox.ts
|
|
2073
2338
|
const Temporal$10 = require_http.Temporal;
|
|
@@ -2751,6 +3016,15 @@ async function sendActivityInternal({ activity, activityId, activityType, keys,
|
|
|
2751
3016
|
if (!allowPrivateAddress) try {
|
|
2752
3017
|
await (0, _fedify_vocab_runtime.validatePublicUrl)(url);
|
|
2753
3018
|
} catch (error) {
|
|
3019
|
+
if (error instanceof _fedify_vocab_runtime.UrlError && error.reason === "dns") {
|
|
3020
|
+
logger.error("DNS resolution failed for URL: {url}", {
|
|
3021
|
+
url,
|
|
3022
|
+
error
|
|
3023
|
+
});
|
|
3024
|
+
const failure = new _fedify_vocab_runtime.FetchError(url, error.message);
|
|
3025
|
+
failure.cause = error;
|
|
3026
|
+
throw failure;
|
|
3027
|
+
}
|
|
2754
3028
|
if (error instanceof _fedify_vocab_runtime.UrlError) logger.error("Disallowed private URL: {url}", {
|
|
2755
3029
|
url,
|
|
2756
3030
|
error
|
|
@@ -2758,7 +3032,12 @@ async function sendActivityInternal({ activity, activityId, activityType, keys,
|
|
|
2758
3032
|
throw error;
|
|
2759
3033
|
}
|
|
2760
3034
|
}
|
|
2761
|
-
|
|
3035
|
+
try {
|
|
3036
|
+
await validateUrl(inbox.href);
|
|
3037
|
+
} catch (error) {
|
|
3038
|
+
if (error instanceof _fedify_vocab_runtime.FetchError) federationMetrics.recordDelivery(inbox, require_http.getDurationMs(started), false, activityType);
|
|
3039
|
+
throw error;
|
|
3040
|
+
}
|
|
2762
3041
|
headers = new Headers(headers);
|
|
2763
3042
|
headers.set("Content-Type", "application/activity+json");
|
|
2764
3043
|
const request = new Request(inbox, {
|
|
@@ -3632,6 +3911,46 @@ function portableObjectInternalServerError(request) {
|
|
|
3632
3911
|
headers: { "Content-Type": "text/plain; charset=utf-8" }
|
|
3633
3912
|
});
|
|
3634
3913
|
}
|
|
3914
|
+
/**
|
|
3915
|
+
* Checks the portable objects embedded in a portable collection or its page
|
|
3916
|
+
* that is about to be served against the FEP-ef61 proof policy. See
|
|
3917
|
+
* `verifyServedPortableObjects()`.
|
|
3918
|
+
* @returns `true` if the collection can be served.
|
|
3919
|
+
*/
|
|
3920
|
+
async function verifyPortableCollection(jsonLd, context, portable) {
|
|
3921
|
+
const logger = (0, _logtape_logtape.getLogger)([
|
|
3922
|
+
"fedify",
|
|
3923
|
+
"federation",
|
|
3924
|
+
"collection"
|
|
3925
|
+
]);
|
|
3926
|
+
try {
|
|
3927
|
+
const result = await verifyServedPortableObjects(jsonLd, {
|
|
3928
|
+
contextLoader: context.contextLoader,
|
|
3929
|
+
documentLoader: context.documentLoader,
|
|
3930
|
+
tracerProvider: context.tracerProvider,
|
|
3931
|
+
meterProvider: context.meterProvider
|
|
3932
|
+
});
|
|
3933
|
+
if (result.verified) return true;
|
|
3934
|
+
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.", {
|
|
3935
|
+
collectionId: portable.id.href,
|
|
3936
|
+
objectId: result.id,
|
|
3937
|
+
path: result.path,
|
|
3938
|
+
reason: result.reason
|
|
3939
|
+
});
|
|
3940
|
+
} catch (error) {
|
|
3941
|
+
logger.error("Failed to verify the portable objects embedded in the portable collection {collectionId}:\n{error}", {
|
|
3942
|
+
collectionId: portable.id.href,
|
|
3943
|
+
error
|
|
3944
|
+
});
|
|
3945
|
+
}
|
|
3946
|
+
return false;
|
|
3947
|
+
}
|
|
3948
|
+
function respondWithPortableCollection(request, jsonLd) {
|
|
3949
|
+
return new Response(request.method === "HEAD" ? null : JSON.stringify(jsonLd), { headers: {
|
|
3950
|
+
"Content-Type": PORTABLE_OBJECT_CONTENT_TYPE,
|
|
3951
|
+
Vary: "Accept"
|
|
3952
|
+
} });
|
|
3953
|
+
}
|
|
3635
3954
|
const BUILT_IN_COLLECTION_METRIC_KINDS = /* @__PURE__ */ new Set([
|
|
3636
3955
|
"inbox",
|
|
3637
3956
|
"outbox",
|
|
@@ -3669,13 +3988,13 @@ function recordCollectionMetrics(meterProvider, base, result, options = {}) {
|
|
|
3669
3988
|
* @param parameters The parameters for handling the collection.
|
|
3670
3989
|
* @returns A promise that resolves to an HTTP response.
|
|
3671
3990
|
*/
|
|
3672
|
-
async function handleCollection(request, { name: name$
|
|
3673
|
-
const spanName = name$
|
|
3991
|
+
async function handleCollection(request, { name: name$1, identifier, uriGetter, filter, filterPredicate, context, collectionCallbacks, portable, tracerProvider, meterProvider, onUnauthorized, onNotFound }) {
|
|
3992
|
+
const spanName = name$1.trim().replace(/\s+/g, "_");
|
|
3674
3993
|
tracerProvider = tracerProvider ?? _opentelemetry_api.trace.getTracerProvider();
|
|
3675
3994
|
const tracer = tracerProvider.getTracer(require_http.name, require_http.version);
|
|
3676
3995
|
const cursor = new URL(request.url).searchParams.get("cursor");
|
|
3677
3996
|
const metricBase = {
|
|
3678
|
-
kind: getCollectionMetricKind(name$
|
|
3997
|
+
kind: getCollectionMetricKind(name$1),
|
|
3679
3998
|
page: cursor != null,
|
|
3680
3999
|
dispatcher: "built_in"
|
|
3681
4000
|
};
|
|
@@ -3693,8 +4012,13 @@ async function handleCollection(request, { name: name$2, identifier, uriGetter,
|
|
|
3693
4012
|
};
|
|
3694
4013
|
try {
|
|
3695
4014
|
if (collectionCallbacks == null) return finish(await onNotFound(request), "not_found");
|
|
4015
|
+
if (portable != null && collectionCallbacks.authorizePredicate != null) {
|
|
4016
|
+
if (!await collectionCallbacks.authorizePredicate(context, identifier)) return finish(await onUnauthorized(request), "unauthorized");
|
|
4017
|
+
}
|
|
3696
4018
|
let collection;
|
|
3697
|
-
const baseUri = uriGetter(identifier);
|
|
4019
|
+
const baseUri = portable?.id ?? uriGetter(identifier);
|
|
4020
|
+
const pageBase = portable?.view ?? context.url;
|
|
4021
|
+
const attribution = portable?.attribution ?? null;
|
|
3698
4022
|
if (cursor == null) {
|
|
3699
4023
|
const firstCursor = await collectionCallbacks.firstCursor?.(context, identifier);
|
|
3700
4024
|
const totalItems = filter == null ? await collectionCallbacks.counter?.(context, identifier) : void 0;
|
|
@@ -3716,7 +4040,7 @@ async function handleCollection(request, { name: name$2, identifier, uriGetter,
|
|
|
3716
4040
|
span.setStatus({ code: _opentelemetry_api.SpanStatusCode.ERROR });
|
|
3717
4041
|
return await onNotFound(request);
|
|
3718
4042
|
}
|
|
3719
|
-
const items = filterCollectionItems(page.items, name$
|
|
4043
|
+
const items = filterCollectionItems(page.items, name$1, filterPredicate);
|
|
3720
4044
|
itemCount = items.length;
|
|
3721
4045
|
span.setAttribute("fedify.collection.items", itemCount);
|
|
3722
4046
|
return items;
|
|
@@ -3734,29 +4058,31 @@ async function handleCollection(request, { name: name$2, identifier, uriGetter,
|
|
|
3734
4058
|
if (itemsOrResponse instanceof Response) return finish(itemsOrResponse, "not_found");
|
|
3735
4059
|
collection = new _fedify_vocab.OrderedCollection({
|
|
3736
4060
|
id: baseUri,
|
|
4061
|
+
attribution,
|
|
3737
4062
|
totalItems: totalItemCount ?? null,
|
|
3738
4063
|
items: itemsOrResponse
|
|
3739
4064
|
});
|
|
3740
4065
|
} else {
|
|
3741
4066
|
const lastCursor = await collectionCallbacks.lastCursor?.(context, identifier);
|
|
3742
|
-
const first = new URL(
|
|
4067
|
+
const first = new URL(pageBase);
|
|
3743
4068
|
first.searchParams.set("cursor", firstCursor);
|
|
3744
4069
|
let last = null;
|
|
3745
4070
|
if (lastCursor != null) {
|
|
3746
|
-
last = new URL(
|
|
4071
|
+
last = new URL(pageBase);
|
|
3747
4072
|
last.searchParams.set("cursor", lastCursor);
|
|
3748
4073
|
}
|
|
3749
4074
|
collection = new _fedify_vocab.OrderedCollection({
|
|
3750
4075
|
id: baseUri,
|
|
4076
|
+
attribution,
|
|
3751
4077
|
totalItems: totalItemCount ?? null,
|
|
3752
4078
|
first,
|
|
3753
4079
|
last
|
|
3754
4080
|
});
|
|
3755
4081
|
}
|
|
3756
4082
|
} else {
|
|
3757
|
-
const uri = new URL(baseUri);
|
|
4083
|
+
const uri = new URL(portable == null ? baseUri : pageBase);
|
|
3758
4084
|
uri.searchParams.set("cursor", cursor);
|
|
3759
|
-
const pageOrResponse = await tracer.startActiveSpan(`activitypub.dispatch_collection_page ${name$
|
|
4085
|
+
const pageOrResponse = await tracer.startActiveSpan(`activitypub.dispatch_collection_page ${name$1}`, {
|
|
3760
4086
|
kind: _opentelemetry_api.SpanKind.SERVER,
|
|
3761
4087
|
attributes: {
|
|
3762
4088
|
"activitypub.collection.id": uri.href,
|
|
@@ -3772,7 +4098,7 @@ async function handleCollection(request, { name: name$2, identifier, uriGetter,
|
|
|
3772
4098
|
span.setStatus({ code: _opentelemetry_api.SpanStatusCode.ERROR });
|
|
3773
4099
|
return await onNotFound(request);
|
|
3774
4100
|
}
|
|
3775
|
-
const items = filterCollectionItems(page.items, name$
|
|
4101
|
+
const items = filterCollectionItems(page.items, name$1, filterPredicate);
|
|
3776
4102
|
itemCount = items.length;
|
|
3777
4103
|
span.setAttribute("fedify.collection.items", itemCount);
|
|
3778
4104
|
return {
|
|
@@ -3794,28 +4120,30 @@ async function handleCollection(request, { name: name$2, identifier, uriGetter,
|
|
|
3794
4120
|
const { items, prevCursor, nextCursor } = pageOrResponse;
|
|
3795
4121
|
let prev = null;
|
|
3796
4122
|
if (prevCursor != null) {
|
|
3797
|
-
prev = new URL(
|
|
4123
|
+
prev = new URL(pageBase);
|
|
3798
4124
|
prev.searchParams.set("cursor", prevCursor);
|
|
3799
4125
|
}
|
|
3800
4126
|
let next = null;
|
|
3801
4127
|
if (nextCursor != null) {
|
|
3802
|
-
next = new URL(
|
|
4128
|
+
next = new URL(pageBase);
|
|
3803
4129
|
next.searchParams.set("cursor", nextCursor);
|
|
3804
4130
|
}
|
|
3805
|
-
const partOf = new URL(
|
|
4131
|
+
const partOf = new URL(pageBase);
|
|
3806
4132
|
partOf.searchParams.delete("cursor");
|
|
3807
4133
|
collection = new _fedify_vocab.OrderedCollectionPage({
|
|
3808
4134
|
id: uri,
|
|
4135
|
+
attribution,
|
|
3809
4136
|
prev,
|
|
3810
4137
|
next,
|
|
3811
4138
|
items,
|
|
3812
4139
|
partOf
|
|
3813
4140
|
});
|
|
3814
4141
|
}
|
|
3815
|
-
if (collectionCallbacks.authorizePredicate != null) {
|
|
4142
|
+
if (portable == null && collectionCallbacks.authorizePredicate != null) {
|
|
3816
4143
|
if (!await collectionCallbacks.authorizePredicate(context, identifier)) return finish(await onUnauthorized(request), "unauthorized");
|
|
3817
4144
|
}
|
|
3818
4145
|
const jsonLd = await collection.toJsonLd(context);
|
|
4146
|
+
if (portable != null) return await verifyPortableCollection(jsonLd, context, portable) ? finish(respondWithPortableCollection(request, jsonLd), "served") : finish(portableObjectInternalServerError(request), "error");
|
|
3819
4147
|
return finish(new Response(JSON.stringify(jsonLd), { headers: {
|
|
3820
4148
|
"Content-Type": "application/activity+json",
|
|
3821
4149
|
Vary: "Accept"
|
|
@@ -4863,19 +5191,11 @@ function recordDeferredPendingCollectionMetrics(error, result, response) {
|
|
|
4863
5191
|
pendingCollectionMetricRecorders.delete(error);
|
|
4864
5192
|
recorder?.(result, response);
|
|
4865
5193
|
}
|
|
4866
|
-
async function _handleCustomCollection(request, { name, values, context, tracerProvider, meterProvider, collectionCallbacks: callbacks, filterPredicate }) {
|
|
5194
|
+
async function _handleCustomCollection(request, { name, values, context, tracerProvider, meterProvider, collectionCallbacks: callbacks, filterPredicate, portable }) {
|
|
4867
5195
|
verifyDefined(callbacks);
|
|
4868
5196
|
await authIfNeeded(context, values, callbacks);
|
|
4869
5197
|
const cursor = new URL(request.url).searchParams.get("cursor");
|
|
4870
|
-
|
|
4871
|
-
try {
|
|
4872
|
-
const response = await handler.toJsonLd().then(respondAsActivity);
|
|
4873
|
-
handler.recordPendingCollectionMetrics("served", response);
|
|
4874
|
-
return response;
|
|
4875
|
-
} catch (e) {
|
|
4876
|
-
if (!deferPendingCollectionMetrics(e, (result, response) => handler.recordPendingCollectionMetrics(result, response))) handler.recordPendingCollectionMetrics("error");
|
|
4877
|
-
throw e;
|
|
4878
|
-
}
|
|
5198
|
+
return await respondWithCustomCollection(request, new CustomCollectionHandler(name, values, context, callbacks, tracerProvider, meterProvider, _fedify_vocab.Collection, _fedify_vocab.CollectionPage, filterPredicate, portable).fetchCollection(cursor), context, portable);
|
|
4879
5199
|
}
|
|
4880
5200
|
/**
|
|
4881
5201
|
* Handles an ordered collection request.
|
|
@@ -4889,13 +5209,19 @@ async function _handleCustomCollection(request, { name, values, context, tracerP
|
|
|
4889
5209
|
* @since 1.8.0
|
|
4890
5210
|
*/
|
|
4891
5211
|
const handleOrderedCollection = exceptWrapper(_handleOrderedCollection);
|
|
4892
|
-
async function _handleOrderedCollection(request, { name, values, context, tracerProvider, meterProvider, collectionCallbacks: callbacks, filterPredicate }) {
|
|
5212
|
+
async function _handleOrderedCollection(request, { name, values, context, tracerProvider, meterProvider, collectionCallbacks: callbacks, filterPredicate, portable }) {
|
|
4893
5213
|
verifyDefined(callbacks);
|
|
4894
5214
|
await authIfNeeded(context, values, callbacks);
|
|
4895
5215
|
const cursor = new URL(request.url).searchParams.get("cursor");
|
|
4896
|
-
|
|
5216
|
+
return await respondWithCustomCollection(request, new CustomCollectionHandler(name, values, context, callbacks, tracerProvider, meterProvider, _fedify_vocab.OrderedCollection, _fedify_vocab.OrderedCollectionPage, filterPredicate, portable).fetchCollection(cursor), context, portable);
|
|
5217
|
+
}
|
|
5218
|
+
async function respondWithCustomCollection(request, handler, context, portable) {
|
|
4897
5219
|
try {
|
|
4898
|
-
const
|
|
5220
|
+
const jsonLd = await handler.toJsonLd();
|
|
5221
|
+
let response;
|
|
5222
|
+
if (portable == null) response = respondAsActivity(jsonLd);
|
|
5223
|
+
else if (await verifyPortableCollection(jsonLd, context, portable)) response = respondWithPortableCollection(request, jsonLd);
|
|
5224
|
+
else throw new PortableCollectionRefusedError();
|
|
4899
5225
|
handler.recordPendingCollectionMetrics("served", response);
|
|
4900
5226
|
return response;
|
|
4901
5227
|
} catch (e) {
|
|
@@ -4925,6 +5251,7 @@ var CustomCollectionHandler = class {
|
|
|
4925
5251
|
Collection;
|
|
4926
5252
|
CollectionPage;
|
|
4927
5253
|
filterPredicate;
|
|
5254
|
+
portable;
|
|
4928
5255
|
/**
|
|
4929
5256
|
* The tracer for telemetry.
|
|
4930
5257
|
* @type {Tracer}
|
|
@@ -4961,8 +5288,8 @@ var CustomCollectionHandler = class {
|
|
|
4961
5288
|
* @param CollectionPage The CollectionPage constructor.
|
|
4962
5289
|
* @param filterPredicate Optional filter predicate for items.
|
|
4963
5290
|
*/
|
|
4964
|
-
constructor(name$
|
|
4965
|
-
this.name = name$
|
|
5291
|
+
constructor(name$2, values, context, callbacks, tracerProvider = _opentelemetry_api.trace.getTracerProvider(), meterProvider, Collection, CollectionPage, filterPredicate, portable) {
|
|
5292
|
+
this.name = name$2;
|
|
4966
5293
|
this.values = values;
|
|
4967
5294
|
this.context = context;
|
|
4968
5295
|
this.callbacks = callbacks;
|
|
@@ -4971,9 +5298,14 @@ var CustomCollectionHandler = class {
|
|
|
4971
5298
|
this.Collection = Collection;
|
|
4972
5299
|
this.CollectionPage = CollectionPage;
|
|
4973
5300
|
this.filterPredicate = filterPredicate;
|
|
5301
|
+
this.portable = portable;
|
|
4974
5302
|
this.name = this.name.trim().replace(/\s+/g, "_");
|
|
4975
5303
|
this.#tracer = this.tracerProvider.getTracer(require_http.name, require_http.version);
|
|
4976
|
-
this.#id = new URL(this.context.url);
|
|
5304
|
+
if (portable == null) this.#id = new URL(this.context.url);
|
|
5305
|
+
else {
|
|
5306
|
+
const cursor = this.context.url.searchParams.get("cursor");
|
|
5307
|
+
this.#id = cursor == null ? new URL(portable.view) : appendCursorIfExists(portable.view, cursor);
|
|
5308
|
+
}
|
|
4977
5309
|
this.#dispatcher = callbacks.dispatcher.bind(callbacks);
|
|
4978
5310
|
}
|
|
4979
5311
|
/**
|
|
@@ -5024,6 +5356,7 @@ var CustomCollectionHandler = class {
|
|
|
5024
5356
|
this.recordPendingCollectionItemCount(true, items.length);
|
|
5025
5357
|
return {
|
|
5026
5358
|
id,
|
|
5359
|
+
attribution: this.portable?.attribution ?? null,
|
|
5027
5360
|
partOf,
|
|
5028
5361
|
items,
|
|
5029
5362
|
prev: this.appendToUrl(prevCursor),
|
|
@@ -5045,6 +5378,7 @@ var CustomCollectionHandler = class {
|
|
|
5045
5378
|
});
|
|
5046
5379
|
return {
|
|
5047
5380
|
id: this.#id,
|
|
5381
|
+
attribution: this.portable?.attribution ?? null,
|
|
5048
5382
|
first: this.appendToUrl(firstCursor),
|
|
5049
5383
|
last: this.appendToUrl(lastCursor),
|
|
5050
5384
|
totalItems
|
|
@@ -5061,6 +5395,7 @@ var CustomCollectionHandler = class {
|
|
|
5061
5395
|
this.recordPendingCollectionItemCount(false, items.length);
|
|
5062
5396
|
return {
|
|
5063
5397
|
id: this.#id,
|
|
5398
|
+
attribution: this.portable?.attribution ?? null,
|
|
5064
5399
|
totalItems,
|
|
5065
5400
|
items
|
|
5066
5401
|
};
|
|
@@ -5178,7 +5513,7 @@ var CustomCollectionHandler = class {
|
|
|
5178
5513
|
* @returns The URL with cursor appended, or null if cursor is null/undefined.
|
|
5179
5514
|
*/
|
|
5180
5515
|
appendToUrl(cursor) {
|
|
5181
|
-
return appendCursorIfExists(this.context.url, cursor);
|
|
5516
|
+
return appendCursorIfExists(this.portable?.view ?? this.context.url, cursor);
|
|
5182
5517
|
}
|
|
5183
5518
|
/**
|
|
5184
5519
|
* Gets the stored collection or collection page.
|
|
@@ -5255,6 +5590,12 @@ function exceptWrapper(handler) {
|
|
|
5255
5590
|
require_http.recordCollectionRequest(meterProvider, collectionAttributes(metricBase, "not_found", response));
|
|
5256
5591
|
return response;
|
|
5257
5592
|
}
|
|
5593
|
+
case PortableCollectionRefusedError: {
|
|
5594
|
+
const response = portableObjectInternalServerError(request);
|
|
5595
|
+
recordDeferredPendingCollectionMetrics(error, "error", response);
|
|
5596
|
+
require_http.recordCollectionRequest(meterProvider, collectionAttributes(metricBase, "error", response));
|
|
5597
|
+
return response;
|
|
5598
|
+
}
|
|
5258
5599
|
case UnauthorizedError: {
|
|
5259
5600
|
const response = await onUnauthorized(request);
|
|
5260
5601
|
recordDeferredPendingCollectionMetrics(error, "unauthorized", response);
|
|
@@ -5351,6 +5692,16 @@ var UnauthorizedError = class extends HandlerError {
|
|
|
5351
5692
|
}
|
|
5352
5693
|
};
|
|
5353
5694
|
/**
|
|
5695
|
+
* Error thrown when a portable collection is not served because it embeds
|
|
5696
|
+
* portable objects that do not satisfy the FEP-ef61 proof policy.
|
|
5697
|
+
* @since 2.4.0
|
|
5698
|
+
*/
|
|
5699
|
+
var PortableCollectionRefusedError = class extends HandlerError {
|
|
5700
|
+
constructor() {
|
|
5701
|
+
super("The portable collection embeds unverifiable portable objects.");
|
|
5702
|
+
}
|
|
5703
|
+
};
|
|
5704
|
+
/**
|
|
5354
5705
|
* Responds with the given object in JSON-LD format.
|
|
5355
5706
|
*
|
|
5356
5707
|
* @param object The object to respond with.
|
|
@@ -5808,6 +6159,100 @@ function serializeDataPointType(dataPointType) {
|
|
|
5808
6159
|
}
|
|
5809
6160
|
}
|
|
5810
6161
|
//#endregion
|
|
6162
|
+
//#region src/federation/portable-collection.ts
|
|
6163
|
+
/**
|
|
6164
|
+
* The query parameters of a gateway request for a portable collection that
|
|
6165
|
+
* do not select a view of the collection: the page cursor, and FEP-ef61
|
|
6166
|
+
* location hints (`@gateway`, and the legacy `gateways`).
|
|
6167
|
+
*/
|
|
6168
|
+
const NON_VIEW_PARAMETERS = /* @__PURE__ */ new Set([
|
|
6169
|
+
"cursor",
|
|
6170
|
+
"@gateway",
|
|
6171
|
+
"gateways"
|
|
6172
|
+
]);
|
|
6173
|
+
/**
|
|
6174
|
+
* Checks that a collection requested through the FEP-ef61 gateway endpoint
|
|
6175
|
+
* belongs to a portable actor under the requested DID, and determines
|
|
6176
|
+
* the collection's ID from the actor.
|
|
6177
|
+
*
|
|
6178
|
+
* Collection dispatchers return items, not documents with IDs, so unlike
|
|
6179
|
+
* object dispatchers, there is nothing else to tell whether the requested
|
|
6180
|
+
* DID controls the collection.
|
|
6181
|
+
*
|
|
6182
|
+
* @param actor The actor that owns the collection according to
|
|
6183
|
+
* the application, dispatched without the portable request,
|
|
6184
|
+
* or `null` if there is none.
|
|
6185
|
+
* @param options The requested DID, and either the requested collection's
|
|
6186
|
+
* canonical ID and the actor's property that has to refer to
|
|
6187
|
+
* it (for the built-in collections of an actor), or
|
|
6188
|
+
* the requested collection's portable ID (for a custom
|
|
6189
|
+
* collection, whose ID has the same form as the actor's).
|
|
6190
|
+
* @returns The resolution.
|
|
6191
|
+
*/
|
|
6192
|
+
function resolvePortableCollectionOwner(actor, options) {
|
|
6193
|
+
if (!(0, _fedify_vocab.isActor)(actor)) return {
|
|
6194
|
+
status: "rejected",
|
|
6195
|
+
reason: "notFound"
|
|
6196
|
+
};
|
|
6197
|
+
const ownerId = actor.id;
|
|
6198
|
+
const did = ownerId == null || !require_http.isPortableId(ownerId) ? null : require_http.getPortableDid(ownerId);
|
|
6199
|
+
if (ownerId == null || did == null) return {
|
|
6200
|
+
status: "rejected",
|
|
6201
|
+
reason: "notPortable"
|
|
6202
|
+
};
|
|
6203
|
+
if (did !== options.authority) return {
|
|
6204
|
+
status: "rejected",
|
|
6205
|
+
reason: "authorityMismatch"
|
|
6206
|
+
};
|
|
6207
|
+
if ("portableId" in options) return {
|
|
6208
|
+
status: "accepted",
|
|
6209
|
+
ownerId,
|
|
6210
|
+
collectionId: getCustomCollectionId(ownerId, actor, options.portableId)
|
|
6211
|
+
};
|
|
6212
|
+
const property = options.property(actor);
|
|
6213
|
+
if (property == null || require_http.getCanonicalPortableId(property) !== options.canonicalId) return {
|
|
6214
|
+
status: "rejected",
|
|
6215
|
+
reason: "collectionMismatch"
|
|
6216
|
+
};
|
|
6217
|
+
const collectionId = new URL(property);
|
|
6218
|
+
collectionId.search = "";
|
|
6219
|
+
collectionId.hash = "";
|
|
6220
|
+
return {
|
|
6221
|
+
status: "accepted",
|
|
6222
|
+
ownerId,
|
|
6223
|
+
collectionId
|
|
6224
|
+
};
|
|
6225
|
+
}
|
|
6226
|
+
/**
|
|
6227
|
+
* Gets the ID of a custom portable collection in the same form as its owner's
|
|
6228
|
+
* ID: a portable ID if the owner has one, or otherwise a compatible identifier
|
|
6229
|
+
* on the owner's first gateway, as FEP-ef61 requires of publishers.
|
|
6230
|
+
*/
|
|
6231
|
+
function getCustomCollectionId(ownerId, owner, portableId) {
|
|
6232
|
+
if (require_http.isPortableUri(ownerId)) return portableId;
|
|
6233
|
+
const gateway = owner.gateways[0];
|
|
6234
|
+
if (gateway == null || !(0, _fedify_vocab_runtime.isGatewayUrl)(gateway)) return portableId;
|
|
6235
|
+
try {
|
|
6236
|
+
return (0, _fedify_vocab_runtime.toCompatibleEf61Id)(portableId, gateway);
|
|
6237
|
+
} catch (error) {
|
|
6238
|
+
if (error instanceof TypeError) return portableId;
|
|
6239
|
+
throw error;
|
|
6240
|
+
}
|
|
6241
|
+
}
|
|
6242
|
+
/**
|
|
6243
|
+
* Builds the URI that the pages of a portable collection are relative to:
|
|
6244
|
+
* the collection's ID with the query parameters of the requested view, e.g.,
|
|
6245
|
+
* a filter, but without the page cursor and location hints.
|
|
6246
|
+
* @param collectionId The ID of the collection.
|
|
6247
|
+
* @param requestUrl The URL of the gateway request.
|
|
6248
|
+
* @returns The view URI.
|
|
6249
|
+
*/
|
|
6250
|
+
function buildPortableCollectionView(collectionId, requestUrl) {
|
|
6251
|
+
const view = new URL(collectionId);
|
|
6252
|
+
for (const [name, value] of requestUrl.searchParams) if (!NON_VIEW_PARAMETERS.has(name)) view.searchParams.append(name, value);
|
|
6253
|
+
return view;
|
|
6254
|
+
}
|
|
6255
|
+
//#endregion
|
|
5811
6256
|
//#region src/federation/outgoing-proof.ts
|
|
5812
6257
|
function formatKeyIds(candidates) {
|
|
5813
6258
|
return candidates.map((c) => c.verificationMethod.href).join(", ");
|
|
@@ -8272,12 +8717,7 @@ var FederationImpl = class extends FederationBuilderImpl {
|
|
|
8272
8717
|
onNotFound
|
|
8273
8718
|
});
|
|
8274
8719
|
case "followers": {
|
|
8275
|
-
|
|
8276
|
-
if (baseUrl != null) try {
|
|
8277
|
-
baseUrl = `${new URL(baseUrl).origin}/`;
|
|
8278
|
-
} catch {
|
|
8279
|
-
baseUrl = null;
|
|
8280
|
-
}
|
|
8720
|
+
const baseUrl = getFollowersBaseUrl(url);
|
|
8281
8721
|
return await handleCollection(request, {
|
|
8282
8722
|
name: "followers",
|
|
8283
8723
|
identifier: route.values.identifier,
|
|
@@ -8288,7 +8728,7 @@ var FederationImpl = class extends FederationBuilderImpl {
|
|
|
8288
8728
|
},
|
|
8289
8729
|
context,
|
|
8290
8730
|
filter: baseUrl != null ? new URL(baseUrl) : void 0,
|
|
8291
|
-
filterPredicate: baseUrl != null ? (
|
|
8731
|
+
filterPredicate: baseUrl != null ? getFollowersFilterPredicate(baseUrl) : void 0,
|
|
8292
8732
|
collectionCallbacks: this.followersCallbacks,
|
|
8293
8733
|
tracerProvider: this.tracerProvider,
|
|
8294
8734
|
meterProvider: this._meterProvider,
|
|
@@ -8382,17 +8822,30 @@ var FederationImpl = class extends FederationBuilderImpl {
|
|
|
8382
8822
|
}
|
|
8383
8823
|
const route = this.router.route(portable.path);
|
|
8384
8824
|
const isActor = route != null && (route.name === "actor" || route.name.startsWith("actorAlias:"));
|
|
8385
|
-
|
|
8825
|
+
const collectionRoute = route == null ? void 0 : getCollectionMetricRoute(route.name.replace(/:.*$/, ""));
|
|
8826
|
+
if (route == null || !isActor && !route.name.startsWith("object:") && collectionRoute == null) {
|
|
8386
8827
|
metricState.endpoint = "not_found";
|
|
8387
8828
|
return await onNotFound(request);
|
|
8388
8829
|
}
|
|
8389
8830
|
metricState.routeTemplate = PORTABLE_GATEWAY_ROUTE_PREFIX + route.template;
|
|
8390
|
-
metricState.endpoint =
|
|
8831
|
+
metricState.endpoint = getEndpointCategory(route.name);
|
|
8391
8832
|
span.updateName(`${request.method} ${metricState.routeTemplate}`);
|
|
8392
8833
|
if (!acceptsJsonLd(request)) {
|
|
8393
8834
|
metricState.endpoint = "not_acceptable";
|
|
8394
|
-
|
|
8835
|
+
const response = await onNotAcceptable(request);
|
|
8836
|
+
if (collectionRoute != null) require_http.recordCollectionRequest(this._meterProvider, {
|
|
8837
|
+
...collectionRoute,
|
|
8838
|
+
page: new URL(request.url).searchParams.get("cursor") != null,
|
|
8839
|
+
result: "not_acceptable",
|
|
8840
|
+
statusCode: response.status
|
|
8841
|
+
});
|
|
8842
|
+
return response;
|
|
8395
8843
|
}
|
|
8844
|
+
if (collectionRoute != null) return await this.#fetchPortableCollection(request, portable, route, {
|
|
8845
|
+
onNotFound,
|
|
8846
|
+
onUnauthorized,
|
|
8847
|
+
contextData
|
|
8848
|
+
});
|
|
8396
8849
|
if (isActor) {
|
|
8397
8850
|
const identifier = route.name.startsWith("actorAlias:") ? route.name.substring(11) : route.values.identifier;
|
|
8398
8851
|
const context = this.#createContext(request, contextData, {
|
|
@@ -8431,6 +8884,108 @@ var FederationImpl = class extends FederationBuilderImpl {
|
|
|
8431
8884
|
onNotFound
|
|
8432
8885
|
});
|
|
8433
8886
|
}
|
|
8887
|
+
async #fetchPortableCollection(request, portable, route, { onNotFound, onUnauthorized, contextData }) {
|
|
8888
|
+
const logger = (0, _logtape_logtape.getLogger)([
|
|
8889
|
+
"fedify",
|
|
8890
|
+
"federation",
|
|
8891
|
+
"collection"
|
|
8892
|
+
]);
|
|
8893
|
+
const url = new URL(request.url);
|
|
8894
|
+
const { authority } = portable.portableRequest;
|
|
8895
|
+
const ownerContext = this.#createContext(request, contextData);
|
|
8896
|
+
const context = this.#createContext(request, contextData, { portableRequest: portable.portableRequest });
|
|
8897
|
+
const reject = async (reason, identifier) => {
|
|
8898
|
+
logger.debug("Not serving the portable collection {collection} of the actor {identifier}: {reason}.", {
|
|
8899
|
+
collection: portable.canonicalId,
|
|
8900
|
+
identifier,
|
|
8901
|
+
reason
|
|
8902
|
+
});
|
|
8903
|
+
return await onNotFound(request);
|
|
8904
|
+
};
|
|
8905
|
+
const routeName = route.name.replace(/:.*$/, "");
|
|
8906
|
+
if (routeName === "collection" || routeName === "orderedCollection") {
|
|
8907
|
+
const name = route.name.replace(/^(?:ordered)?[cC]ollection:/, "");
|
|
8908
|
+
const callbacks = this.collectionCallbacks[name];
|
|
8909
|
+
if (callbacks == null) return await onNotFound(request);
|
|
8910
|
+
const mapper = callbacks.portableOwnerMapper;
|
|
8911
|
+
if (mapper == null) return await reject("noPortableOwnerMapper");
|
|
8912
|
+
const identifier = await mapper(ownerContext, route.values);
|
|
8913
|
+
if (identifier == null) return await reject("notPortable");
|
|
8914
|
+
const resolution = resolvePortableCollectionOwner(await this.actorCallbacks?.dispatcher?.(ownerContext, identifier), {
|
|
8915
|
+
authority,
|
|
8916
|
+
portableId: portable.portableRequest.id
|
|
8917
|
+
});
|
|
8918
|
+
if (resolution.status === "rejected") return await reject(resolution.reason, identifier);
|
|
8919
|
+
const view = buildPortableCollectionView(resolution.collectionId, url);
|
|
8920
|
+
return await (routeName === "collection" ? handleCustomCollection : handleOrderedCollection)(request, {
|
|
8921
|
+
name,
|
|
8922
|
+
context,
|
|
8923
|
+
values: route.values,
|
|
8924
|
+
collectionCallbacks: callbacks,
|
|
8925
|
+
portable: {
|
|
8926
|
+
id: view,
|
|
8927
|
+
view,
|
|
8928
|
+
attribution: resolution.ownerId
|
|
8929
|
+
},
|
|
8930
|
+
tracerProvider: this.tracerProvider,
|
|
8931
|
+
meterProvider: this._meterProvider,
|
|
8932
|
+
onUnauthorized,
|
|
8933
|
+
onNotFound
|
|
8934
|
+
});
|
|
8935
|
+
}
|
|
8936
|
+
const identifier = route.values.identifier;
|
|
8937
|
+
const serve = async (name, callbacks, property, filter) => {
|
|
8938
|
+
if (callbacks == null) return await onNotFound(request);
|
|
8939
|
+
const resolution = resolvePortableCollectionOwner(await this.actorCallbacks?.dispatcher?.(ownerContext, identifier), {
|
|
8940
|
+
authority,
|
|
8941
|
+
canonicalId: portable.canonicalId,
|
|
8942
|
+
property
|
|
8943
|
+
});
|
|
8944
|
+
if (resolution.status === "rejected") return await reject(resolution.reason, identifier);
|
|
8945
|
+
const id = new URL(resolution.collectionId);
|
|
8946
|
+
if (filter != null) id.searchParams.set(...filter.parameter);
|
|
8947
|
+
const view = buildPortableCollectionView(resolution.collectionId, url);
|
|
8948
|
+
if (routeName === "followers") {
|
|
8949
|
+
view.searchParams.delete("base-url");
|
|
8950
|
+
if (filter != null) view.searchParams.set(...filter.parameter);
|
|
8951
|
+
}
|
|
8952
|
+
return await handleCollection(request, {
|
|
8953
|
+
name,
|
|
8954
|
+
identifier,
|
|
8955
|
+
uriGetter: () => id,
|
|
8956
|
+
context,
|
|
8957
|
+
filter: filter?.value,
|
|
8958
|
+
filterPredicate: filter?.predicate,
|
|
8959
|
+
collectionCallbacks: callbacks,
|
|
8960
|
+
portable: {
|
|
8961
|
+
id,
|
|
8962
|
+
view,
|
|
8963
|
+
attribution: resolution.ownerId
|
|
8964
|
+
},
|
|
8965
|
+
tracerProvider: this.tracerProvider,
|
|
8966
|
+
meterProvider: this._meterProvider,
|
|
8967
|
+
onUnauthorized,
|
|
8968
|
+
onNotFound
|
|
8969
|
+
});
|
|
8970
|
+
};
|
|
8971
|
+
switch (routeName) {
|
|
8972
|
+
case "outbox": return await serve("outbox", this.outboxCallbacks, (a) => a.outboxId);
|
|
8973
|
+
case "inbox": return await serve("inbox", this.inboxCallbacks, (a) => a.inboxId);
|
|
8974
|
+
case "following": return await serve("following", this.followingCallbacks, (a) => a.followingId);
|
|
8975
|
+
case "followers": {
|
|
8976
|
+
const baseUrl = getFollowersBaseUrl(url);
|
|
8977
|
+
return await serve("followers", this.followersCallbacks, (a) => a.followersId, baseUrl == null ? void 0 : {
|
|
8978
|
+
value: new URL(baseUrl),
|
|
8979
|
+
predicate: getFollowersFilterPredicate(baseUrl),
|
|
8980
|
+
parameter: ["base-url", baseUrl]
|
|
8981
|
+
});
|
|
8982
|
+
}
|
|
8983
|
+
case "liked": return await serve("liked", this.likedCallbacks, (a) => a.likedId);
|
|
8984
|
+
case "featured": return await serve("featured", this.featuredCallbacks, (a) => a.featuredId);
|
|
8985
|
+
case "featuredTags": return await serve("featured tags", this.featuredTagsCallbacks, (a) => a.featuredTagsId);
|
|
8986
|
+
}
|
|
8987
|
+
return await onNotFound(request);
|
|
8988
|
+
}
|
|
8434
8989
|
async #handleInbox(request, { recipient, context, contextData, onNotFound, portableInbox }) {
|
|
8435
8990
|
if (!this.manuallyStartQueue) this._startQueueInternal(contextData);
|
|
8436
8991
|
const inboxContextFactory = context.toInboxContext.bind(context);
|
|
@@ -8617,6 +9172,41 @@ var FederationImpl = class extends FederationBuilderImpl {
|
|
|
8617
9172
|
};
|
|
8618
9173
|
const PORTABLE_GATEWAY_ROUTE_PREFIX = "/.well-known/apgateway/{did}";
|
|
8619
9174
|
/**
|
|
9175
|
+
* Gets the origin that a followers collection request filters followers by,
|
|
9176
|
+
* i.e., its normalized `base-url` query parameter (FEP-8fcf).
|
|
9177
|
+
* @returns The origin with a trailing slash, or `null` if the parameter is
|
|
9178
|
+
* missing or invalid.
|
|
9179
|
+
*/
|
|
9180
|
+
function getFollowersBaseUrl(url) {
|
|
9181
|
+
const baseUrl = url.searchParams.get("base-url");
|
|
9182
|
+
if (baseUrl == null) return null;
|
|
9183
|
+
try {
|
|
9184
|
+
return `${new URL(baseUrl).origin}/`;
|
|
9185
|
+
} catch {
|
|
9186
|
+
return null;
|
|
9187
|
+
}
|
|
9188
|
+
}
|
|
9189
|
+
function getFollowersFilterPredicate(baseUrl) {
|
|
9190
|
+
return (i) => (i instanceof URL ? i.href : i.id?.href ?? "").startsWith(baseUrl);
|
|
9191
|
+
}
|
|
9192
|
+
const ACTOR_COLLECTION_ROUTE_ERRORS = {
|
|
9193
|
+
outbox: "No outbox dispatcher registered.",
|
|
9194
|
+
following: "No following collection path registered.",
|
|
9195
|
+
followers: "No followers collection path registered.",
|
|
9196
|
+
liked: "No liked collection path registered.",
|
|
9197
|
+
featured: "No featured collection path registered.",
|
|
9198
|
+
featuredTags: "No featured tags collection path registered."
|
|
9199
|
+
};
|
|
9200
|
+
/**
|
|
9201
|
+
* Checks that the authority of a portable ID to build is given, either
|
|
9202
|
+
* explicitly or by the FEP-ef61 gateway request being handled.
|
|
9203
|
+
* @throws {TypeError} If the authority is missing.
|
|
9204
|
+
*/
|
|
9205
|
+
function requirePortableAuthority(authority) {
|
|
9206
|
+
if (authority == null) throw new TypeError("The authority of a portable ID is required outside an FEP-ef61 gateway request.");
|
|
9207
|
+
return authority;
|
|
9208
|
+
}
|
|
9209
|
+
/**
|
|
8620
9210
|
* Removes the body of a response to a `HEAD` request.
|
|
8621
9211
|
* @param request The request.
|
|
8622
9212
|
* @param response The response to the request.
|
|
@@ -8795,8 +9385,7 @@ var ContextImpl = class ContextImpl {
|
|
|
8795
9385
|
}
|
|
8796
9386
|
getPortableActorUri(identifier, authority) {
|
|
8797
9387
|
const path = this.#getActorPath(identifier);
|
|
8798
|
-
|
|
8799
|
-
return buildPortableUri(authority, path);
|
|
9388
|
+
return buildPortableUri(requirePortableAuthority(authority), path);
|
|
8800
9389
|
}
|
|
8801
9390
|
#getActorPath(identifier) {
|
|
8802
9391
|
const path = this.federation.router.build(`actorAlias:${identifier}`, {}) ?? this.federation.router.build("actor", { identifier });
|
|
@@ -8808,8 +9397,7 @@ var ContextImpl = class ContextImpl {
|
|
|
8808
9397
|
}
|
|
8809
9398
|
getPortableObjectUri(cls, values, authority) {
|
|
8810
9399
|
const path = this.#getObjectPath(cls, values);
|
|
8811
|
-
|
|
8812
|
-
return buildPortableUri(authority, path);
|
|
9400
|
+
return buildPortableUri(requirePortableAuthority(authority), path);
|
|
8813
9401
|
}
|
|
8814
9402
|
#getObjectPath(cls, values) {
|
|
8815
9403
|
const callbacks = this.federation.objectCallbacks[cls.typeId.href];
|
|
@@ -8819,10 +9407,17 @@ var ContextImpl = class ContextImpl {
|
|
|
8819
9407
|
if (path == null) throw new _fedify_uri_template.RouterError("No object dispatcher registered.");
|
|
8820
9408
|
return path;
|
|
8821
9409
|
}
|
|
9410
|
+
#getActorCollectionPath(route, identifier) {
|
|
9411
|
+
const path = this.federation.router.build(route, { identifier });
|
|
9412
|
+
if (path == null) throw new _fedify_uri_template.RouterError(ACTOR_COLLECTION_ROUTE_ERRORS[route]);
|
|
9413
|
+
return path;
|
|
9414
|
+
}
|
|
8822
9415
|
getOutboxUri(identifier) {
|
|
8823
|
-
|
|
8824
|
-
|
|
8825
|
-
|
|
9416
|
+
return new URL(this.#getActorCollectionPath("outbox", identifier), this.canonicalOrigin);
|
|
9417
|
+
}
|
|
9418
|
+
getPortableOutboxUri(identifier, authority) {
|
|
9419
|
+
const path = this.#getActorCollectionPath("outbox", identifier);
|
|
9420
|
+
return buildPortableUri(requirePortableAuthority(authority), path);
|
|
8826
9421
|
}
|
|
8827
9422
|
getMediaUploaderUri(identifier) {
|
|
8828
9423
|
const path = this.federation.router.build("mediaUploader", { identifier });
|
|
@@ -8842,39 +9437,53 @@ var ContextImpl = class ContextImpl {
|
|
|
8842
9437
|
getPortableInboxUri(identifier, authority) {
|
|
8843
9438
|
const path = this.federation.router.build("inbox", { identifier });
|
|
8844
9439
|
if (path == null) throw new _fedify_uri_template.RouterError("No inbox path registered.");
|
|
8845
|
-
|
|
8846
|
-
return buildPortableUri(authority, path);
|
|
9440
|
+
return buildPortableUri(requirePortableAuthority(authority), path);
|
|
8847
9441
|
}
|
|
8848
9442
|
getFollowingUri(identifier) {
|
|
8849
|
-
|
|
8850
|
-
|
|
8851
|
-
|
|
9443
|
+
return new URL(this.#getActorCollectionPath("following", identifier), this.canonicalOrigin);
|
|
9444
|
+
}
|
|
9445
|
+
getPortableFollowingUri(identifier, authority) {
|
|
9446
|
+
const path = this.#getActorCollectionPath("following", identifier);
|
|
9447
|
+
return buildPortableUri(requirePortableAuthority(authority), path);
|
|
8852
9448
|
}
|
|
8853
9449
|
getFollowersUri(identifier) {
|
|
8854
|
-
|
|
8855
|
-
|
|
8856
|
-
|
|
9450
|
+
return new URL(this.#getActorCollectionPath("followers", identifier), this.canonicalOrigin);
|
|
9451
|
+
}
|
|
9452
|
+
getPortableFollowersUri(identifier, authority) {
|
|
9453
|
+
const path = this.#getActorCollectionPath("followers", identifier);
|
|
9454
|
+
return buildPortableUri(requirePortableAuthority(authority), path);
|
|
8857
9455
|
}
|
|
8858
9456
|
getLikedUri(identifier) {
|
|
8859
|
-
|
|
8860
|
-
|
|
8861
|
-
|
|
9457
|
+
return new URL(this.#getActorCollectionPath("liked", identifier), this.canonicalOrigin);
|
|
9458
|
+
}
|
|
9459
|
+
getPortableLikedUri(identifier, authority) {
|
|
9460
|
+
const path = this.#getActorCollectionPath("liked", identifier);
|
|
9461
|
+
return buildPortableUri(requirePortableAuthority(authority), path);
|
|
8862
9462
|
}
|
|
8863
9463
|
getFeaturedUri(identifier) {
|
|
8864
|
-
|
|
8865
|
-
|
|
8866
|
-
|
|
9464
|
+
return new URL(this.#getActorCollectionPath("featured", identifier), this.canonicalOrigin);
|
|
9465
|
+
}
|
|
9466
|
+
getPortableFeaturedUri(identifier, authority) {
|
|
9467
|
+
const path = this.#getActorCollectionPath("featured", identifier);
|
|
9468
|
+
return buildPortableUri(requirePortableAuthority(authority), path);
|
|
8867
9469
|
}
|
|
8868
9470
|
getFeaturedTagsUri(identifier) {
|
|
8869
|
-
|
|
8870
|
-
|
|
8871
|
-
|
|
9471
|
+
return new URL(this.#getActorCollectionPath("featuredTags", identifier), this.canonicalOrigin);
|
|
9472
|
+
}
|
|
9473
|
+
getPortableFeaturedTagsUri(identifier, authority) {
|
|
9474
|
+
const path = this.#getActorCollectionPath("featuredTags", identifier);
|
|
9475
|
+
return buildPortableUri(requirePortableAuthority(authority), path);
|
|
8872
9476
|
}
|
|
8873
9477
|
getCollectionUri(name, values) {
|
|
8874
9478
|
const path = this.federation.getCollectionPath(name, values);
|
|
8875
9479
|
if (path === null) throw new _fedify_uri_template.RouterError(`No collection dispatcher registered for "${String(name)}".`);
|
|
8876
9480
|
return new URL(path, this.canonicalOrigin);
|
|
8877
9481
|
}
|
|
9482
|
+
getPortableCollectionUri(name, values, authority) {
|
|
9483
|
+
const path = this.federation.getCollectionPath(name, values);
|
|
9484
|
+
if (path === null) throw new _fedify_uri_template.RouterError(`No collection dispatcher registered for "${String(name)}".`);
|
|
9485
|
+
return buildPortableUri(requirePortableAuthority(authority), path);
|
|
9486
|
+
}
|
|
8878
9487
|
parseUri(uri) {
|
|
8879
9488
|
if (uri == null) return null;
|
|
8880
9489
|
if (uri.origin !== this.origin && uri.origin !== this.canonicalOrigin) return null;
|
|
@@ -9454,6 +10063,27 @@ var RequestContextImpl = class RequestContextImpl extends ContextImpl {
|
|
|
9454
10063
|
getPortableInboxUri(identifier, authority) {
|
|
9455
10064
|
return super.getPortableInboxUri(identifier, authority ?? this.portableRequest?.authority);
|
|
9456
10065
|
}
|
|
10066
|
+
getPortableOutboxUri(identifier, authority) {
|
|
10067
|
+
return super.getPortableOutboxUri(identifier, authority ?? this.portableRequest?.authority);
|
|
10068
|
+
}
|
|
10069
|
+
getPortableFollowingUri(identifier, authority) {
|
|
10070
|
+
return super.getPortableFollowingUri(identifier, authority ?? this.portableRequest?.authority);
|
|
10071
|
+
}
|
|
10072
|
+
getPortableFollowersUri(identifier, authority) {
|
|
10073
|
+
return super.getPortableFollowersUri(identifier, authority ?? this.portableRequest?.authority);
|
|
10074
|
+
}
|
|
10075
|
+
getPortableLikedUri(identifier, authority) {
|
|
10076
|
+
return super.getPortableLikedUri(identifier, authority ?? this.portableRequest?.authority);
|
|
10077
|
+
}
|
|
10078
|
+
getPortableFeaturedUri(identifier, authority) {
|
|
10079
|
+
return super.getPortableFeaturedUri(identifier, authority ?? this.portableRequest?.authority);
|
|
10080
|
+
}
|
|
10081
|
+
getPortableFeaturedTagsUri(identifier, authority) {
|
|
10082
|
+
return super.getPortableFeaturedTagsUri(identifier, authority ?? this.portableRequest?.authority);
|
|
10083
|
+
}
|
|
10084
|
+
getPortableCollectionUri(name, values, authority) {
|
|
10085
|
+
return super.getPortableCollectionUri(name, values, authority ?? this.portableRequest?.authority);
|
|
10086
|
+
}
|
|
9457
10087
|
async getActor(identifier, options) {
|
|
9458
10088
|
if (this.federation.actorCallbacks == null || this.federation.actorCallbacks.dispatcher == null) throw new Error("No actor dispatcher registered.");
|
|
9459
10089
|
if (this.#invokedFromActorDispatcher != null) (0, _logtape_logtape.getLogger)([
|