@fedify/fedify 2.4.0-dev.2200 → 2.4.0-dev.2219
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/audience-CHQ-FlLD.mjs +95 -0
- package/dist/{builder-BGSNMnVH.mjs → builder-C5rYBZ16.mjs} +10 -2
- package/dist/compat/mod.d.cts +1 -1
- package/dist/compat/mod.d.ts +1 -1
- package/dist/compat/public-audience.test.mjs +1 -1
- package/dist/compat/transformers.test.mjs +1 -1
- package/dist/{compound-proof-Zpg7Fbk9.mjs → compound-proof-D0xY7MBs.mjs} +2 -2
- package/dist/{context-B2rlkkyJ.mjs → context-8Tu9kea0.mjs} +2 -0
- package/dist/{context-CDcAgm4l.d.cts → context-B6X-t8gI.d.cts} +81 -2
- package/dist/{context-9T--NSo4.d.ts → context-CtI307_A.d.ts} +81 -2
- package/dist/{deno-CxoV48e0.mjs → deno-Dj1TFhf4.mjs} +1 -1
- package/dist/{docloader-U141HKIl.mjs → docloader-CzyJe90o.mjs} +3 -3
- package/dist/federation/audience.test.d.mts +2 -0
- package/dist/federation/audience.test.mjs +130 -0
- package/dist/federation/builder.test.mjs +37 -1
- package/dist/federation/compound-fanout.test.mjs +4 -4
- package/dist/federation/handler-compound-proof.test.mjs +5 -5
- package/dist/federation/handler.test.mjs +7 -7
- package/dist/federation/hashlink-media.test.mjs +3 -3
- package/dist/federation/idempotency.test.mjs +2 -2
- package/dist/federation/metrics.test.mjs +2 -2
- package/dist/federation/middleware.test.mjs +125 -9
- package/dist/federation/mod.cjs +1 -1
- package/dist/federation/mod.d.cts +4 -4
- package/dist/federation/mod.d.ts +4 -4
- package/dist/federation/mod.js +1 -1
- package/dist/federation/outgoing-proof.test.mjs +6 -6
- package/dist/federation/portable-actor.test.mjs +4 -4
- package/dist/federation/portable-chain.test.mjs +4 -4
- package/dist/federation/portable-collection.test.mjs +8 -6
- package/dist/federation/portable-delivery.test.d.mts +2 -0
- package/dist/federation/portable-delivery.test.mjs +746 -0
- package/dist/federation/portable-gateway-keys.test.mjs +6 -6
- package/dist/federation/portable-inbox.test.mjs +5 -5
- package/dist/federation/portable-parse-uri.test.mjs +2 -2
- package/dist/federation/portable-route-activity.test.mjs +3 -3
- package/dist/federation/portable.test.mjs +234 -19
- package/dist/federation/send.test.mjs +4 -4
- package/dist/federation/tasks/enqueue.test.mjs +2 -2
- package/dist/federation/tasks/tasks.test.mjs +3 -3
- package/dist/federation/temporal.test.mjs +1 -1
- package/dist/federation/webfinger.test.mjs +3 -3
- package/dist/{federation-Ij8VLHpy.cjs → federation-DC1yfb-8.cjs} +696 -41
- package/dist/{http-D8XakCKn.cjs → http-Cuk0w0rp.cjs} +1 -1
- package/dist/{http-BQq1Uldg.mjs → http-D-DpP4sd.mjs} +5 -5
- package/dist/{http-CshNXXui.js → http-Dx7yMmfw.js} +1 -1
- package/dist/{key-DYgoirTo.mjs → key-Djyt1BFx.mjs} +2 -2
- package/dist/{kv-BhNbN8Ed.d.cts → kv-B2mzK5tl.d.cts} +3 -2
- package/dist/{kv-BhNbN8Ed.d.ts → kv-B2mzK5tl.d.ts} +3 -2
- package/dist/{kv-cache-f_GNq0e7.cjs → kv-cache-BmLTVLXc.cjs} +1 -1
- package/dist/{kv-cache-Bpnt_Cck.js → kv-cache-CUZOCSKZ.js} +1 -1
- package/dist/{kv-cache-D0Mn-VQW.mjs → kv-cache-ecUXfCqW.mjs} +1 -1
- package/dist/{ld-eIekvNtm.mjs → ld-CNVWdooi.mjs} +3 -3
- package/dist/{metrics-BVpdjsaR.mjs → metrics-BnYleVpg.mjs} +1 -1
- package/dist/{middleware-CS3H_4tQ.js → middleware-BdzcazaN.js} +698 -43
- package/dist/{middleware-rpYJG85a.mjs → middleware-Bh592bk8.mjs} +1 -1
- package/dist/{middleware-BbTJ6C1-.mjs → middleware-C2_LcbL3.mjs} +431 -50
- package/dist/{mod-C5Mwx5ty.d.ts → mod-B8MI1mpi.d.ts} +1 -1
- package/dist/{mod-B3OBLj5g.d.ts → mod-BhTaDR5H.d.ts} +1 -1
- package/dist/{mod-D58KCqUg.d.cts → mod-C7_2j7VT.d.cts} +1 -1
- package/dist/{mod-Dp3Ij7sw.d.cts → mod-DFL6IxYQ.d.cts} +1 -1
- package/dist/mod.cjs +4 -4
- package/dist/mod.d.cts +6 -6
- package/dist/mod.d.ts +6 -6
- package/dist/mod.js +4 -4
- package/dist/nodeinfo/client.test.mjs +1 -1
- package/dist/nodeinfo/handler.test.mjs +2 -2
- package/dist/otel/mod.d.cts +1 -1
- package/dist/otel/mod.d.ts +1 -1
- package/dist/{owner-DpxyMeo9.mjs → owner-D_BDPwm2.mjs} +3 -3
- package/dist/{portable-collection-BvJ5om91.js → portable-collection-C0c0rUN9.js} +1 -1
- package/dist/{portable-collection-DyJwtPz1.cjs → portable-collection-DwJ8YJP2.cjs} +1 -1
- package/dist/{portable-collection-DOL3l2C9.mjs → portable-collection-hcONGyUs.mjs} +2 -2
- package/dist/{portable-key-B8kTV4n8.mjs → portable-key-B880yVlC.mjs} +1 -1
- package/dist/{proof-B9DEHHiO.mjs → proof-z6Prgi5I.mjs} +4 -4
- package/dist/{send-CZckda2X.mjs → send-Cw545L_2.mjs} +191 -10
- 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 +3 -3
- package/dist/sig/compound-proof.test.mjs +3 -3
- package/dist/sig/http.test.mjs +3 -3
- package/dist/sig/key-owner-evidence.test.mjs +2 -2
- package/dist/sig/key.test.mjs +2 -2
- package/dist/sig/ld.test.mjs +2 -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-id.test.mjs +2 -2
- package/dist/sig/portable-key.test.mjs +7 -7
- package/dist/sig/proof.test.mjs +4 -4
- package/dist/{temporal-Cc1ymEup.mjs → temporal-Cy-8WCod.mjs} +1 -1
- package/dist/testing/kv.test.mjs +1 -1
- package/dist/testing/mod.d.mts +82 -2
- package/dist/testing/mod.mjs +2 -2
- package/dist/{testing-C1itIjqW.mjs → testing-t8xDeJZ5.mjs} +1 -1
- package/dist/utils/docloader.test.mjs +6 -6
- package/dist/utils/kv-cache.test.mjs +2 -2
- 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/package.json +7 -7
|
@@ -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-
|
|
4
|
+
const require_http = require("./http-Cuk0w0rp.cjs");
|
|
5
5
|
const require_types = require("./types-poIao40f.cjs");
|
|
6
|
-
const require_portable_collection = require("./portable-collection-
|
|
7
|
-
const require_kv_cache = require("./kv-cache-
|
|
6
|
+
const require_portable_collection = require("./portable-collection-DwJ8YJP2.cjs");
|
|
7
|
+
const require_kv_cache = require("./kv-cache-BmLTVLXc.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");
|
|
@@ -118,6 +118,7 @@ var FederationBuilderImpl = class {
|
|
|
118
118
|
* Symbol registry for unique identification of unnamed symbols.
|
|
119
119
|
*/
|
|
120
120
|
#symbolRegistry = /* @__PURE__ */ new Map();
|
|
121
|
+
#collectionNames = /* @__PURE__ */ new Map();
|
|
121
122
|
constructor() {
|
|
122
123
|
this.router = new _fedify_uri_template.Router();
|
|
123
124
|
this.objectCallbacks = {};
|
|
@@ -147,6 +148,8 @@ var FederationBuilderImpl = class {
|
|
|
147
148
|
f.objectTypeIds = { ...this.objectTypeIds };
|
|
148
149
|
f.collectionCallbacks = { ...this.collectionCallbacks };
|
|
149
150
|
f.collectionTypeIds = { ...this.collectionTypeIds };
|
|
151
|
+
f.#symbolRegistry = new Map(this.#symbolRegistry);
|
|
152
|
+
f.#collectionNames = new Map(this.#collectionNames);
|
|
150
153
|
f.inboxPath = this.inboxPath;
|
|
151
154
|
f.outboxPath = this.outboxPath;
|
|
152
155
|
f.inboxCallbacks = this.inboxCallbacks == null ? void 0 : { ...this.inboxCallbacks };
|
|
@@ -678,6 +681,7 @@ var FederationBuilderImpl = class {
|
|
|
678
681
|
if (this.collectionCallbacks[name] != null) throw new _fedify_uri_template.RouterError(`Collection dispatcher for ${strName} already set.`);
|
|
679
682
|
(0, _fedify_uri_template.assertPath)(path);
|
|
680
683
|
if (_fedify_uri_template.Router.variables(path).size < 1) throw new _fedify_uri_template.RouterError("Path for collection dispatcher must have at least one variable.");
|
|
684
|
+
this.#collectionNames.set(routeName, name);
|
|
681
685
|
this.router.add(path, routeName);
|
|
682
686
|
const callbacks = { dispatcher };
|
|
683
687
|
this.collectionCallbacks[name] = callbacks;
|
|
@@ -719,6 +723,10 @@ var FederationBuilderImpl = class {
|
|
|
719
723
|
const routeName = this.#uniqueCollectionId(name);
|
|
720
724
|
return this.router.build(`collection:${routeName}`, values) ?? this.router.build(`orderedCollection:${routeName}`, values);
|
|
721
725
|
}
|
|
726
|
+
/** Resolves a custom collection route to its original name. */
|
|
727
|
+
getCollectionName(routeName) {
|
|
728
|
+
return this.#collectionNames.get(routeName) ?? routeName.replace(/^(collection|orderedCollection):/, "");
|
|
729
|
+
}
|
|
722
730
|
setOutboxPermanentFailureHandler(handler) {
|
|
723
731
|
this.outboxPermanentFailureHandler = handler;
|
|
724
732
|
}
|
|
@@ -2359,6 +2367,95 @@ async function checkServedPortableMap({ map, contexts }, options) {
|
|
|
2359
2367
|
return UNSECURED_CORE_TYPES.has(type) ? null : "missingProof";
|
|
2360
2368
|
}
|
|
2361
2369
|
//#endregion
|
|
2370
|
+
//#region src/federation/audience.ts
|
|
2371
|
+
const AS_NAMESPACE = "https://www.w3.org/ns/activitystreams#";
|
|
2372
|
+
/**
|
|
2373
|
+
* The addressing properties that determine the audience of an object.
|
|
2374
|
+
*/
|
|
2375
|
+
const ADDRESSING_PROPERTIES = [
|
|
2376
|
+
"to",
|
|
2377
|
+
"cc",
|
|
2378
|
+
"bto",
|
|
2379
|
+
"bcc",
|
|
2380
|
+
"audience"
|
|
2381
|
+
];
|
|
2382
|
+
/**
|
|
2383
|
+
* The IDs that Fedify treats as the public collection. Besides the full IRI,
|
|
2384
|
+
* the compact forms `as:Public` and `Public` are reserved as public for
|
|
2385
|
+
* compatibility with documents whose compact forms survive JSON-LD expansion,
|
|
2386
|
+
* e.g., bare `Public`, which expands to a relative IRI.
|
|
2387
|
+
*/
|
|
2388
|
+
const PUBLIC_IDS = /* @__PURE__ */ new Set([
|
|
2389
|
+
_fedify_vocab.PUBLIC_COLLECTION.href,
|
|
2390
|
+
"as:Public",
|
|
2391
|
+
"Public"
|
|
2392
|
+
]);
|
|
2393
|
+
/**
|
|
2394
|
+
* Checks whether an expanded JSON-LD node is addressed to the public
|
|
2395
|
+
* collection, i.e., any of its `to`, `cc`, `bto`, `bcc`, and `audience` has
|
|
2396
|
+
* the public collection. Only the node itself is checked, not the nodes
|
|
2397
|
+
* embedded in it.
|
|
2398
|
+
* @param node The expanded JSON-LD node.
|
|
2399
|
+
* @returns `true` if the node is publicly addressed.
|
|
2400
|
+
* @internal
|
|
2401
|
+
*/
|
|
2402
|
+
function isPubliclyAddressedNode(node) {
|
|
2403
|
+
for (const property of ADDRESSING_PROPERTIES) {
|
|
2404
|
+
const values = node[AS_NAMESPACE + property];
|
|
2405
|
+
if (!Array.isArray(values)) continue;
|
|
2406
|
+
for (const value of values) if (typeof value === "object" && value != null && "@id" in value && typeof value["@id"] === "string" && PUBLIC_IDS.has(value["@id"])) return true;
|
|
2407
|
+
}
|
|
2408
|
+
return false;
|
|
2409
|
+
}
|
|
2410
|
+
function getAudienceIds(object) {
|
|
2411
|
+
return [
|
|
2412
|
+
...object.toIds,
|
|
2413
|
+
...object.ccIds,
|
|
2414
|
+
...object.btoIds,
|
|
2415
|
+
...object.bccIds,
|
|
2416
|
+
...object.audienceIds
|
|
2417
|
+
];
|
|
2418
|
+
}
|
|
2419
|
+
/**
|
|
2420
|
+
* Checks whether two actor IDs refer to the same actor. [FEP-ef61] portable
|
|
2421
|
+
* IDs are compared by their canonical forms, so an `ap:` URI, an `ap+ef61:`
|
|
2422
|
+
* URI, and a compatible identifier on any gateway are the same actor if they
|
|
2423
|
+
* have the same canonical portable ID. Other IDs are compared as they are.
|
|
2424
|
+
*
|
|
2425
|
+
* [FEP-ef61]: https://w3id.org/fep/ef61
|
|
2426
|
+
*/
|
|
2427
|
+
function isSameActorId(a, b) {
|
|
2428
|
+
const canonicalA = require_http.getCanonicalPortableId(a);
|
|
2429
|
+
const canonicalB = require_http.getCanonicalPortableId(b);
|
|
2430
|
+
if (canonicalA != null || canonicalB != null) return canonicalA === canonicalB;
|
|
2431
|
+
return a.href === b.href;
|
|
2432
|
+
}
|
|
2433
|
+
/**
|
|
2434
|
+
* Checks whether an actor belongs to the audience of an object: the object is
|
|
2435
|
+
* publicly addressed, the actor is one of its addressees, or the actor is
|
|
2436
|
+
* a member of one of them according to `isMember()`.
|
|
2437
|
+
* @param object The object whose audience is checked.
|
|
2438
|
+
* @param actor The actor to check, or `null` if there is no actor. No actor
|
|
2439
|
+
* belongs to the audience of an object that is not public.
|
|
2440
|
+
* @param options Options for checking the audience.
|
|
2441
|
+
* @returns `true` if the actor belongs to the audience of the object.
|
|
2442
|
+
* @internal
|
|
2443
|
+
*/
|
|
2444
|
+
async function isInAudience(object, actor, options = {}) {
|
|
2445
|
+
const addressees = getAudienceIds(object);
|
|
2446
|
+
if (addressees.some((id) => PUBLIC_IDS.has(id.href))) return true;
|
|
2447
|
+
const actorId = actor?.id;
|
|
2448
|
+
if (actor == null || actorId == null) return false;
|
|
2449
|
+
const others = [];
|
|
2450
|
+
for (const addressee of addressees) {
|
|
2451
|
+
if (isSameActorId(addressee, actorId)) return true;
|
|
2452
|
+
others.push(addressee);
|
|
2453
|
+
}
|
|
2454
|
+
if (options.isMember == null) return false;
|
|
2455
|
+
for (const addressee of others) if (await options.isMember(addressee, actor)) return true;
|
|
2456
|
+
return false;
|
|
2457
|
+
}
|
|
2458
|
+
//#endregion
|
|
2362
2459
|
//#region src/federation/inbox.ts
|
|
2363
2460
|
const Temporal$10 = require_http.Temporal;
|
|
2364
2461
|
async function routeActivity({ context: ctx, json, originalJson, normalizedActivity, ldSignatureVerified, activity, recipient, inboxListeners, inboxContextFactory, listenerInboxContextFactory, inboxErrorHandler, kv, kvPrefixes, queue, span, meterProvider, tracerProvider, idempotencyStrategy }) {
|
|
@@ -2983,31 +3080,177 @@ function parseHashlinkGatewayRequest(url) {
|
|
|
2983
3080
|
})
|
|
2984
3081
|
};
|
|
2985
3082
|
}
|
|
3083
|
+
/**
|
|
3084
|
+
* Resolves a portable inbox, i.e., an `ap:` or `ap+ef61:` URI, to
|
|
3085
|
+
* the compatible identifiers of the inbox on the recipient's gateways.
|
|
3086
|
+
*
|
|
3087
|
+
* The gateways are taken from the recipient's `gateways`, or, if it has no
|
|
3088
|
+
* valid gateway, from the `@gateway` location hints of the inbox URI. Both
|
|
3089
|
+
* come from the recipient object, so they are as trustworthy as its inbox.
|
|
3090
|
+
* Gateways whose origins are in `excludeBaseUris` are skipped.
|
|
3091
|
+
* @param recipient The recipient that owns the inbox.
|
|
3092
|
+
* @param inbox The portable inbox.
|
|
3093
|
+
* @param excludeBaseUris The base URIs whose origins are not delivered to.
|
|
3094
|
+
* @returns The delivery target, or `null` if the inbox is malformed, or no
|
|
3095
|
+
* gateway is left to deliver through.
|
|
3096
|
+
*/
|
|
3097
|
+
function resolvePortableDeliveryTarget(recipient, inbox, excludeBaseUris) {
|
|
3098
|
+
const logger = (0, _logtape_logtape.getLogger)([
|
|
3099
|
+
"fedify",
|
|
3100
|
+
"federation",
|
|
3101
|
+
"outbox"
|
|
3102
|
+
]);
|
|
3103
|
+
let portableInbox;
|
|
3104
|
+
try {
|
|
3105
|
+
portableInbox = (0, _fedify_vocab_runtime.canonicalizePortableUri)(inbox.href);
|
|
3106
|
+
} catch (error) {
|
|
3107
|
+
if (!(error instanceof TypeError)) throw error;
|
|
3108
|
+
logger.warn("Skipping the recipient {recipient}, as its portable inbox {inbox} is malformed.", {
|
|
3109
|
+
recipient: recipient.id?.href,
|
|
3110
|
+
inbox: inbox.href,
|
|
3111
|
+
error
|
|
3112
|
+
});
|
|
3113
|
+
return null;
|
|
3114
|
+
}
|
|
3115
|
+
let gateways = (recipient.gateways ?? []).filter(_fedify_vocab_runtime.isGatewayUrl);
|
|
3116
|
+
if (gateways.length < 1) gateways = (0, _fedify_vocab_runtime_internal_portable_dereference.getPortableGatewayCandidates)(inbox);
|
|
3117
|
+
if (gateways.length < 1) {
|
|
3118
|
+
logger.warn("Skipping the recipient {recipient}, as it has no gateway to deliver to its portable inbox {inbox} through.", {
|
|
3119
|
+
recipient: recipient.id?.href,
|
|
3120
|
+
inbox: portableInbox
|
|
3121
|
+
});
|
|
3122
|
+
return null;
|
|
3123
|
+
}
|
|
3124
|
+
const excludedOrigins = new Set(excludeBaseUris?.map((u) => u.origin));
|
|
3125
|
+
const seen = /* @__PURE__ */ new Set();
|
|
3126
|
+
const inboxes = [];
|
|
3127
|
+
for (const gateway of gateways) {
|
|
3128
|
+
if (inboxes.length >= 5) break;
|
|
3129
|
+
if (seen.has(gateway.origin)) continue;
|
|
3130
|
+
seen.add(gateway.origin);
|
|
3131
|
+
if (excludedOrigins.has(gateway.origin)) continue;
|
|
3132
|
+
try {
|
|
3133
|
+
inboxes.push((0, _fedify_vocab_runtime.toCompatibleEf61Id)(inbox, gateway.origin));
|
|
3134
|
+
} catch (error) {
|
|
3135
|
+
if (!(error instanceof TypeError)) throw error;
|
|
3136
|
+
logger.warn("Cannot deliver to the portable inbox {inbox} through the gateway {gateway}, as the inbox cannot be represented as its compatible identifier.", {
|
|
3137
|
+
inbox: portableInbox,
|
|
3138
|
+
gateway: gateway.origin,
|
|
3139
|
+
error
|
|
3140
|
+
});
|
|
3141
|
+
}
|
|
3142
|
+
}
|
|
3143
|
+
if (inboxes.length < 1) {
|
|
3144
|
+
logger.debug("Skipping the recipient {recipient}, as no gateway is left to deliver to its portable inbox {inbox} through.", {
|
|
3145
|
+
recipient: recipient.id?.href,
|
|
3146
|
+
inbox: portableInbox
|
|
3147
|
+
});
|
|
3148
|
+
return null;
|
|
3149
|
+
}
|
|
3150
|
+
return {
|
|
3151
|
+
portableInbox,
|
|
3152
|
+
inboxes
|
|
3153
|
+
};
|
|
3154
|
+
}
|
|
3155
|
+
/**
|
|
3156
|
+
* Merges two ordered lists of compatible inbox URLs, keeping the first URL
|
|
3157
|
+
* for each gateway origin and at most {@link MAX_DELIVERY_GATEWAYS} URLs.
|
|
3158
|
+
* @param a The preferred list.
|
|
3159
|
+
* @param b The other list.
|
|
3160
|
+
* @returns The merged list.
|
|
3161
|
+
*/
|
|
3162
|
+
function mergeGatewayInboxes(a, b) {
|
|
3163
|
+
const seen = /* @__PURE__ */ new Set();
|
|
3164
|
+
const merged = [];
|
|
3165
|
+
for (const inbox of [...a, ...b]) {
|
|
3166
|
+
if (merged.length >= 5) break;
|
|
3167
|
+
const { origin } = new URL(inbox);
|
|
3168
|
+
if (seen.has(origin)) continue;
|
|
3169
|
+
seen.add(origin);
|
|
3170
|
+
merged.push(inbox);
|
|
3171
|
+
}
|
|
3172
|
+
return merged;
|
|
3173
|
+
}
|
|
3174
|
+
/**
|
|
3175
|
+
* Gets the part of a delivery's ordering key that tells its destination
|
|
3176
|
+
* apart: the DID of a portable inbox, so that deliveries to it stay in order
|
|
3177
|
+
* whichever gateway they go through, or the origin of any other inbox.
|
|
3178
|
+
* @param inbox The inbox URL.
|
|
3179
|
+
* @param portableInbox The canonical portable inbox, if the inbox is
|
|
3180
|
+
* the compatible identifier of one.
|
|
3181
|
+
* @returns The destination part of the ordering key.
|
|
3182
|
+
*/
|
|
3183
|
+
function getOrderingDestination(inbox, portableInbox) {
|
|
3184
|
+
return portableInbox == null ? new URL(inbox).origin : (0, _fedify_vocab_runtime.getFe34Origin)(portableInbox);
|
|
3185
|
+
}
|
|
2986
3186
|
//#endregion
|
|
2987
3187
|
//#region src/federation/send.ts
|
|
2988
3188
|
/**
|
|
2989
3189
|
* Extracts the inbox URLs from recipients.
|
|
3190
|
+
*
|
|
3191
|
+
* An [FEP-ef61] portable inbox, i.e., an `ap:` or `ap+ef61:` URI, is resolved
|
|
3192
|
+
* to its compatible identifiers on the recipient's gateways, or, if the
|
|
3193
|
+
* recipient has no valid gateway, on the gateways in the `@gateway` location
|
|
3194
|
+
* hints of the inbox URI. The inbox is keyed by the compatible identifier on
|
|
3195
|
+
* the first gateway, and the others are listed in
|
|
3196
|
+
* {@link ExtractedInbox.gatewayInboxes}. A recipient whose portable inbox
|
|
3197
|
+
* has no gateway to deliver through is skipped. The shared inbox of
|
|
3198
|
+
* a recipient with a portable inbox is never preferred, since only a portable
|
|
3199
|
+
* inbox is synchronized between the gateways.
|
|
3200
|
+
*
|
|
3201
|
+
* [FEP-ef61]: https://w3id.org/fep/ef61
|
|
2990
3202
|
* @param parameters The parameters to extract the inboxes.
|
|
2991
3203
|
* See also {@link ExtractInboxesParameters}.
|
|
2992
3204
|
* @returns The inboxes as a map of inbox URL to actor URIs.
|
|
2993
3205
|
*/
|
|
2994
3206
|
function extractInboxes({ recipients, preferSharedInbox, excludeBaseUris }) {
|
|
2995
3207
|
const inboxes = {};
|
|
3208
|
+
const portableKeys = /* @__PURE__ */ new Map();
|
|
3209
|
+
const compatibleKeys = /* @__PURE__ */ new Map();
|
|
2996
3210
|
for (const recipient of recipients) {
|
|
3211
|
+
if (recipient.id == null) continue;
|
|
2997
3212
|
let inbox;
|
|
2998
3213
|
let sharedInbox = false;
|
|
2999
|
-
if (preferSharedInbox && recipient.endpoints?.sharedInbox != null) {
|
|
3214
|
+
if (preferSharedInbox && recipient.endpoints?.sharedInbox != null && (recipient.inboxId == null || !require_http.isPortableUri(recipient.inboxId))) {
|
|
3000
3215
|
inbox = recipient.endpoints.sharedInbox;
|
|
3001
3216
|
sharedInbox = true;
|
|
3002
3217
|
} else inbox = recipient.inboxId;
|
|
3003
|
-
if (inbox
|
|
3004
|
-
|
|
3005
|
-
|
|
3218
|
+
if (inbox == null) continue;
|
|
3219
|
+
if (require_http.isPortableUri(inbox)) {
|
|
3220
|
+
const target = resolvePortableDeliveryTarget(recipient, inbox, excludeBaseUris);
|
|
3221
|
+
if (target == null) continue;
|
|
3222
|
+
const gatewayInboxes = target.inboxes.map((u) => u.href);
|
|
3223
|
+
const existingKeys = /* @__PURE__ */ new Set();
|
|
3224
|
+
const portableKey = portableKeys.get(target.portableInbox);
|
|
3225
|
+
if (portableKey != null) existingKeys.add(portableKey);
|
|
3226
|
+
for (const i of gatewayInboxes) {
|
|
3227
|
+
const k = compatibleKeys.get(i) ?? i;
|
|
3228
|
+
if (Object.hasOwn(inboxes, k)) existingKeys.add(k);
|
|
3229
|
+
}
|
|
3230
|
+
const [key = gatewayInboxes[0], ...otherKeys] = existingKeys;
|
|
3231
|
+
portableKeys.set(target.portableInbox, key);
|
|
3232
|
+
const entry = inboxes[key] ??= {
|
|
3006
3233
|
actorIds: /* @__PURE__ */ new Set(),
|
|
3007
3234
|
sharedInbox
|
|
3008
3235
|
};
|
|
3009
|
-
|
|
3236
|
+
for (const k of otherKeys) {
|
|
3237
|
+
for (const actorId of inboxes[k].actorIds) entry.actorIds.add(actorId);
|
|
3238
|
+
delete inboxes[k];
|
|
3239
|
+
}
|
|
3240
|
+
entry.sharedInbox = false;
|
|
3241
|
+
entry.portableInbox = target.portableInbox;
|
|
3242
|
+
entry.gatewayInboxes = mergeGatewayInboxes(entry.gatewayInboxes ?? [key], gatewayInboxes);
|
|
3243
|
+
for (const i of entry.gatewayInboxes) compatibleKeys.set(i, key);
|
|
3244
|
+
entry.actorIds.add(recipient.id.href);
|
|
3245
|
+
continue;
|
|
3010
3246
|
}
|
|
3247
|
+
if (excludeBaseUris != null && excludeBaseUris.some((u) => u.origin === inbox?.origin)) continue;
|
|
3248
|
+
const key = compatibleKeys.get(inbox.href) ?? inbox.href;
|
|
3249
|
+
inboxes[key] ??= {
|
|
3250
|
+
actorIds: /* @__PURE__ */ new Set(),
|
|
3251
|
+
sharedInbox
|
|
3252
|
+
};
|
|
3253
|
+
inboxes[key].actorIds.add(recipient.id.href);
|
|
3011
3254
|
}
|
|
3012
3255
|
return inboxes;
|
|
3013
3256
|
}
|
|
@@ -3042,6 +3285,38 @@ function sendActivity(options) {
|
|
|
3042
3285
|
}
|
|
3043
3286
|
});
|
|
3044
3287
|
}
|
|
3288
|
+
/**
|
|
3289
|
+
* Sends an activity to the first of the given inboxes that accepts it, i.e.,
|
|
3290
|
+
* the compatible identifiers of a portable inbox on its gateways, trying them
|
|
3291
|
+
* one after another.
|
|
3292
|
+
* @param parameters The parameters, except for the inbox.
|
|
3293
|
+
* @param inboxes The inboxes to try, in order. Must not be empty.
|
|
3294
|
+
* @throws {Error} The error of the last inbox if no inbox accepts it.
|
|
3295
|
+
*/
|
|
3296
|
+
async function sendActivityThroughGateways(parameters, inboxes) {
|
|
3297
|
+
const logger = (0, _logtape_logtape.getLogger)([
|
|
3298
|
+
"fedify",
|
|
3299
|
+
"federation",
|
|
3300
|
+
"outbox"
|
|
3301
|
+
]);
|
|
3302
|
+
let lastError;
|
|
3303
|
+
for (const [i, inbox] of inboxes.entries()) try {
|
|
3304
|
+
await sendActivity({
|
|
3305
|
+
...parameters,
|
|
3306
|
+
inbox
|
|
3307
|
+
});
|
|
3308
|
+
return;
|
|
3309
|
+
} catch (error) {
|
|
3310
|
+
lastError = error;
|
|
3311
|
+
if (i < inboxes.length - 1) logger.warn("Failed to send activity {activityId} to {inbox}; trying the next gateway {next}:\n{error}", {
|
|
3312
|
+
activityId: parameters.activityId,
|
|
3313
|
+
inbox: inbox.href,
|
|
3314
|
+
next: inboxes[i + 1].href,
|
|
3315
|
+
error
|
|
3316
|
+
});
|
|
3317
|
+
}
|
|
3318
|
+
throw lastError;
|
|
3319
|
+
}
|
|
3045
3320
|
const MAX_ERROR_RESPONSE_BODY_BYTES = 1024;
|
|
3046
3321
|
function getActivityActorId(activity) {
|
|
3047
3322
|
if (!isRecord(activity)) return void 0;
|
|
@@ -3897,6 +4172,15 @@ async function handleObject(request, { values, context, objectDispatcher, author
|
|
|
3897
4172
|
* only if its ID canonically matches the requested portable ID and it
|
|
3898
4173
|
* satisfies the FEP-ef61 proof policy.
|
|
3899
4174
|
*
|
|
4175
|
+
* FEP-ef61 forbids serving a non-public object to anyone but its intended
|
|
4176
|
+
* audience. If the dispatcher has an authorization predicate, it decides who
|
|
4177
|
+
* may retrieve the object, and the response is marked with
|
|
4178
|
+
* `Cache-Control: private`, as it depends on the requester. Otherwise, only
|
|
4179
|
+
* a publicly addressed object is served, and any other object is responded
|
|
4180
|
+
* to with `404 Not Found`, as if this server did not store it. Actors, and
|
|
4181
|
+
* anything the actor dispatcher returns, are exempt from the latter, as
|
|
4182
|
+
* actors have no audience.
|
|
4183
|
+
*
|
|
3900
4184
|
* A {@link Tombstone} is served with `410 Gone` if it has an Object Integrity
|
|
3901
4185
|
* Proof made with a key of the DID in its ID. An unsigned tombstone cannot
|
|
3902
4186
|
* be served as a portable object, so the response is the same as for
|
|
@@ -3906,7 +4190,7 @@ async function handleObject(request, { values, context, objectDispatcher, author
|
|
|
3906
4190
|
* @param parameters The parameters for handling the portable object.
|
|
3907
4191
|
* @returns A promise that resolves to an HTTP response.
|
|
3908
4192
|
*/
|
|
3909
|
-
async function handlePortableObject(request, { values, context, objectDispatcher, authorizePredicate, canonicalId, onNotFound, onUnauthorized }) {
|
|
4193
|
+
async function handlePortableObject(request, { values, context, objectDispatcher, authorizePredicate, canonicalId, kind, onNotFound, onUnauthorized }) {
|
|
3910
4194
|
const logger = (0, _logtape_logtape.getLogger)([
|
|
3911
4195
|
"fedify",
|
|
3912
4196
|
"federation",
|
|
@@ -3925,14 +4209,32 @@ async function handlePortableObject(request, { values, context, objectDispatcher
|
|
|
3925
4209
|
if (authorizePredicate != null) {
|
|
3926
4210
|
if (!await authorizePredicate(context, values)) return await onUnauthorized(request);
|
|
3927
4211
|
}
|
|
4212
|
+
const contextLoader = require_http.getNormalizationContextLoader(context.contextLoader);
|
|
3928
4213
|
let jsonLd;
|
|
3929
|
-
let
|
|
3930
|
-
let result;
|
|
4214
|
+
let root;
|
|
3931
4215
|
try {
|
|
3932
4216
|
jsonLd = await object.toJsonLd(context);
|
|
3933
|
-
|
|
4217
|
+
root = await expandJsonLdRoot(jsonLd, contextLoader);
|
|
4218
|
+
} catch (error) {
|
|
4219
|
+
logger.error("Failed to serialize the portable object {portableId}:\n{error}", {
|
|
4220
|
+
portableId: canonicalId,
|
|
4221
|
+
error
|
|
4222
|
+
});
|
|
4223
|
+
return portableObjectInternalServerError(request);
|
|
4224
|
+
}
|
|
4225
|
+
const servedId = root?.["@id"];
|
|
4226
|
+
if (root == null || typeof servedId !== "string" || !isRequestedPortableId(servedId, canonicalId)) {
|
|
4227
|
+
logger.debug("The serialized object does not match the requested portable object {portableId}.", { portableId: canonicalId });
|
|
4228
|
+
return await onNotFound(request);
|
|
4229
|
+
}
|
|
4230
|
+
if (authorizePredicate == null && kind === "object" && !(0, _fedify_vocab.isActor)(object) && !isPubliclyAddressedNode(root)) {
|
|
4231
|
+
logger.debug("Not serving the portable object {portableId}, as it is not publicly addressed and its object dispatcher has no authorization predicate; responding as if this server did not store the object. Set an authorization predicate that checks if the request is signed by an actor in the object's audience to serve it.", { portableId: canonicalId });
|
|
4232
|
+
return await onNotFound(request);
|
|
4233
|
+
}
|
|
4234
|
+
let result;
|
|
4235
|
+
try {
|
|
3934
4236
|
result = await require_http.verifyPortableObjectProof(jsonLd, {
|
|
3935
|
-
contextLoader
|
|
4237
|
+
contextLoader,
|
|
3936
4238
|
documentLoader: context.documentLoader,
|
|
3937
4239
|
tracerProvider: context.tracerProvider,
|
|
3938
4240
|
meterProvider: context.meterProvider
|
|
@@ -3944,10 +4246,6 @@ async function handlePortableObject(request, { values, context, objectDispatcher
|
|
|
3944
4246
|
});
|
|
3945
4247
|
return portableObjectInternalServerError(request);
|
|
3946
4248
|
}
|
|
3947
|
-
if (servedId == null || !isRequestedPortableId(servedId, canonicalId)) {
|
|
3948
|
-
logger.debug("The serialized object does not match the requested portable object {portableId}.", { portableId: canonicalId });
|
|
3949
|
-
return await onNotFound(request);
|
|
3950
|
-
}
|
|
3951
4249
|
const tombstone = object instanceof _fedify_vocab.Tombstone;
|
|
3952
4250
|
if (!result.verified) {
|
|
3953
4251
|
if (tombstone && result.reason.type === "missingProof") {
|
|
@@ -3966,25 +4264,26 @@ async function handlePortableObject(request, { values, context, objectDispatcher
|
|
|
3966
4264
|
return portableObjectInternalServerError(request);
|
|
3967
4265
|
}
|
|
3968
4266
|
}
|
|
4267
|
+
const headers = new Headers({
|
|
4268
|
+
"Content-Type": PORTABLE_OBJECT_CONTENT_TYPE,
|
|
4269
|
+
Vary: "Accept"
|
|
4270
|
+
});
|
|
4271
|
+
if (authorizePredicate != null) headers.set("Cache-Control", "private");
|
|
3969
4272
|
return new Response(request.method === "HEAD" ? null : JSON.stringify(jsonLd), {
|
|
3970
4273
|
status: tombstone ? 410 : 200,
|
|
3971
|
-
headers
|
|
3972
|
-
"Content-Type": PORTABLE_OBJECT_CONTENT_TYPE,
|
|
3973
|
-
Vary: "Accept"
|
|
3974
|
-
}
|
|
4274
|
+
headers
|
|
3975
4275
|
});
|
|
3976
4276
|
}
|
|
3977
4277
|
function isRequestedPortableObject(object, canonicalId) {
|
|
3978
4278
|
return object.id != null && require_http.getCanonicalPortableId(object.id) === canonicalId;
|
|
3979
4279
|
}
|
|
3980
|
-
async function
|
|
4280
|
+
async function expandJsonLdRoot(jsonLd, contextLoader) {
|
|
3981
4281
|
const expanded = await _fedify_vocab_runtime_jsonld.default.expand(jsonLd, {
|
|
3982
|
-
documentLoader:
|
|
4282
|
+
documentLoader: contextLoader,
|
|
3983
4283
|
keepFreeFloatingNodes: true
|
|
3984
4284
|
});
|
|
3985
4285
|
if (expanded.length !== 1) return null;
|
|
3986
|
-
|
|
3987
|
-
return typeof id === "string" ? id : null;
|
|
4286
|
+
return expanded[0];
|
|
3988
4287
|
}
|
|
3989
4288
|
function isRequestedPortableId(id, canonicalId) {
|
|
3990
4289
|
if (!/^ap(?:\+ef61)?:\/\//i.test(id)) return URL.canParse(id) && require_http.getCanonicalPortableId(new URL(id)) === canonicalId;
|
|
@@ -7230,6 +7529,37 @@ function getBenchmarkTriggerOptions(benchmarkOptions) {
|
|
|
7230
7529
|
function maxDelay(first, second) {
|
|
7231
7530
|
return Temporal$2.Duration.compare(first, second) >= 0 ? first : second;
|
|
7232
7531
|
}
|
|
7532
|
+
/**
|
|
7533
|
+
* Gets the fields of a queued message that describe the FEP-ef61 portable
|
|
7534
|
+
* inbox an inbox stands for, if any, omitting the absent ones.
|
|
7535
|
+
*/
|
|
7536
|
+
function getPortableMessageFields(portableInbox, gatewayInboxes) {
|
|
7537
|
+
return {
|
|
7538
|
+
...portableInbox == null ? {} : { portableInbox },
|
|
7539
|
+
...gatewayInboxes == null ? {} : { gatewayInboxes: [...gatewayInboxes] }
|
|
7540
|
+
};
|
|
7541
|
+
}
|
|
7542
|
+
/**
|
|
7543
|
+
* Sends an activity to an inbox, or, if the inbox is the compatible
|
|
7544
|
+
* identifier of an FEP-ef61 portable inbox on more than one gateway, through
|
|
7545
|
+
* the first gateway that accepts it.
|
|
7546
|
+
*/
|
|
7547
|
+
function sendActivityToInbox(inbox, gatewayInboxes, parameters) {
|
|
7548
|
+
if (gatewayInboxes == null || gatewayInboxes.length < 2) return sendActivity({
|
|
7549
|
+
...parameters,
|
|
7550
|
+
inbox: new URL(inbox)
|
|
7551
|
+
});
|
|
7552
|
+
return sendActivityThroughGateways(parameters, gatewayInboxes.map((i) => new URL(i)));
|
|
7553
|
+
}
|
|
7554
|
+
function parseInstant(value) {
|
|
7555
|
+
if (value == null) return void 0;
|
|
7556
|
+
try {
|
|
7557
|
+
return Temporal$2.Instant.from(value);
|
|
7558
|
+
} catch (error) {
|
|
7559
|
+
if (error instanceof RangeError) return void 0;
|
|
7560
|
+
throw error;
|
|
7561
|
+
}
|
|
7562
|
+
}
|
|
7233
7563
|
function isTransportDeliveryError(error) {
|
|
7234
7564
|
return error instanceof _fedify_vocab_runtime.FetchError || require_http.isAbortError(error);
|
|
7235
7565
|
}
|
|
@@ -7810,6 +8140,20 @@ var FederationImpl = class extends FederationBuilderImpl {
|
|
|
7810
8140
|
}
|
|
7811
8141
|
require_http.recordOutboxActivity(this.meterProvider, "abandoned", message.activityType);
|
|
7812
8142
|
};
|
|
8143
|
+
const { gatewayInboxes } = message;
|
|
8144
|
+
if (gatewayInboxes != null && gatewayInboxes.length > 1) {
|
|
8145
|
+
await this.#listenGatewayOutboxMessage(_, {
|
|
8146
|
+
...message,
|
|
8147
|
+
gatewayInboxes
|
|
8148
|
+
}, span, {
|
|
8149
|
+
keys,
|
|
8150
|
+
logData,
|
|
8151
|
+
getActorIds,
|
|
8152
|
+
parseActivity,
|
|
8153
|
+
dropHeldOutboxMessage
|
|
8154
|
+
});
|
|
8155
|
+
return;
|
|
8156
|
+
}
|
|
7813
8157
|
try {
|
|
7814
8158
|
const inbox = new URL(message.inbox);
|
|
7815
8159
|
const circuit = this.outboxQueue == null ? void 0 : this.circuitBreaker;
|
|
@@ -8032,6 +8376,308 @@ var FederationImpl = class extends FederationBuilderImpl {
|
|
|
8032
8376
|
}
|
|
8033
8377
|
logger.info("Successfully sent activity {activityId} to {inbox}.", { ...logData });
|
|
8034
8378
|
}
|
|
8379
|
+
/**
|
|
8380
|
+
* Delivers a queued activity to an FEP-ef61 portable inbox through the first
|
|
8381
|
+
* of its gateways that accepts it, trying them one after another in a single
|
|
8382
|
+
* round, so that the round counts as one attempt for the retry policy, keeps
|
|
8383
|
+
* the message's place in its ordering key, and is replayed as a whole by
|
|
8384
|
+
* queues that retry natively.
|
|
8385
|
+
*
|
|
8386
|
+
* A gateway that fails with a permanent failure status is not tried again.
|
|
8387
|
+
* A gateway whose circuit is open, or which asked to be retried later with
|
|
8388
|
+
* `Retry-After`, is skipped in this round. If no gateway accepts the
|
|
8389
|
+
* activity, the message is held while every remaining gateway is held, and
|
|
8390
|
+
* otherwise retried according to the retry policy.
|
|
8391
|
+
*/
|
|
8392
|
+
async #listenGatewayOutboxMessage(contextData, message, span, helpers) {
|
|
8393
|
+
const logger = (0, _logtape_logtape.getLogger)([
|
|
8394
|
+
"fedify",
|
|
8395
|
+
"federation",
|
|
8396
|
+
"outbox"
|
|
8397
|
+
]);
|
|
8398
|
+
const circuitLogger = (0, _logtape_logtape.getLogger)([
|
|
8399
|
+
"fedify",
|
|
8400
|
+
"federation",
|
|
8401
|
+
"circuit"
|
|
8402
|
+
]);
|
|
8403
|
+
const { keys, logData, getActorIds, parseActivity, dropHeldOutboxMessage } = helpers;
|
|
8404
|
+
const circuit = this.outboxQueue == null ? void 0 : this.circuitBreaker;
|
|
8405
|
+
const specDeterminer = new KvSpecDeterminer(this.kv, this.kvPrefixes.httpMessageSignaturesSpec, this.firstKnock, { specTtl: this.httpMessageSignaturesSpecTtl });
|
|
8406
|
+
const survivors = [];
|
|
8407
|
+
let lastError;
|
|
8408
|
+
let lastPermanent;
|
|
8409
|
+
let activity;
|
|
8410
|
+
const getActivity = async () => activity ??= await parseActivity();
|
|
8411
|
+
const drop = async (circuit, remoteHost, inbox, heldSince) => {
|
|
8412
|
+
await dropHeldOutboxMessage(circuit, remoteHost, inbox, heldSince, await getActivity());
|
|
8413
|
+
};
|
|
8414
|
+
const heldSince = parseInstant(message.circuitHeldSince);
|
|
8415
|
+
if (circuit != null && heldSince != null && Temporal$2.Instant.compare(heldSince.add(circuit.options.heldActivityTtl), Temporal$2.Now.instant()) <= 0) {
|
|
8416
|
+
const inbox = new URL(message.gatewayInboxes[0]);
|
|
8417
|
+
await drop(circuit, require_http.getRemoteHost(inbox), inbox, heldSince);
|
|
8418
|
+
return;
|
|
8419
|
+
}
|
|
8420
|
+
for (const inboxHref of message.gatewayInboxes) {
|
|
8421
|
+
const inbox = new URL(inboxHref);
|
|
8422
|
+
const remoteHost = require_http.getRemoteHost(inbox);
|
|
8423
|
+
const now = Temporal$2.Now.instant();
|
|
8424
|
+
const notBefore = parseInstant(message.gatewayNotBefore?.[inboxHref]);
|
|
8425
|
+
if (notBefore != null && Temporal$2.Instant.compare(notBefore, now) > 0) {
|
|
8426
|
+
survivors.push({
|
|
8427
|
+
inbox: inboxHref,
|
|
8428
|
+
holdDelay: now.until(notBefore),
|
|
8429
|
+
notBefore
|
|
8430
|
+
});
|
|
8431
|
+
continue;
|
|
8432
|
+
}
|
|
8433
|
+
let decision;
|
|
8434
|
+
if (circuit != null) try {
|
|
8435
|
+
decision = await circuit.beforeSend(remoteHost, message);
|
|
8436
|
+
} catch (circuitError) {
|
|
8437
|
+
circuitLogger.error("Failed to check circuit breaker state before sending; proceeding with delivery:\n{error}", {
|
|
8438
|
+
...logData,
|
|
8439
|
+
remoteHost,
|
|
8440
|
+
error: circuitError
|
|
8441
|
+
});
|
|
8442
|
+
}
|
|
8443
|
+
if (decision != null && circuit != null) {
|
|
8444
|
+
if (decision.type === "drop") {
|
|
8445
|
+
await drop(circuit, remoteHost, inbox, decision.heldSince);
|
|
8446
|
+
return;
|
|
8447
|
+
} else if (decision.type === "hold") {
|
|
8448
|
+
recordCircuitBreakerHeldSpanEvent(span, remoteHost, decision.state);
|
|
8449
|
+
survivors.push({
|
|
8450
|
+
inbox: inboxHref,
|
|
8451
|
+
holdDelay: decision.delay,
|
|
8452
|
+
circuitHeld: true
|
|
8453
|
+
});
|
|
8454
|
+
continue;
|
|
8455
|
+
} else if (decision.stateChange != null) recordCircuitBreakerSpanEvent(span, remoteHost, decision.stateChange);
|
|
8456
|
+
}
|
|
8457
|
+
try {
|
|
8458
|
+
await sendActivity({
|
|
8459
|
+
allowPrivateAddress: this.allowPrivateAddress,
|
|
8460
|
+
keys,
|
|
8461
|
+
activity: message.activity,
|
|
8462
|
+
activityId: message.activityId,
|
|
8463
|
+
activityType: message.activityType,
|
|
8464
|
+
inbox,
|
|
8465
|
+
sharedInbox: message.sharedInbox,
|
|
8466
|
+
headers: new Headers(message.headers),
|
|
8467
|
+
specDeterminer,
|
|
8468
|
+
meterProvider: this.meterProvider,
|
|
8469
|
+
tracerProvider: this.tracerProvider
|
|
8470
|
+
});
|
|
8471
|
+
} catch (error) {
|
|
8472
|
+
lastError = error;
|
|
8473
|
+
const isPermanentFailure = error instanceof SendActivityError && this.permanentFailureStatusCodes.includes(error.statusCode);
|
|
8474
|
+
let retryAfter = !isPermanentFailure && error instanceof SendActivityError && (error.statusCode === 429 || error.statusCode === 503) ? parseRetryAfter(error.responseHeaders) : void 0;
|
|
8475
|
+
let retryAt;
|
|
8476
|
+
if (retryAfter != null) try {
|
|
8477
|
+
retryAt = Temporal$2.Now.instant().add(retryAfter);
|
|
8478
|
+
} catch (e) {
|
|
8479
|
+
if (!(e instanceof RangeError)) throw e;
|
|
8480
|
+
retryAfter = void 0;
|
|
8481
|
+
}
|
|
8482
|
+
let postDecision;
|
|
8483
|
+
if (circuit != null) try {
|
|
8484
|
+
if (error instanceof SendActivityError) {
|
|
8485
|
+
const { statusCode } = error;
|
|
8486
|
+
const stateChange = isPermanentFailure || statusCode === 429 || statusCode >= 400 && statusCode < 500 ? await circuit.recordReachableFailure(remoteHost) : statusCode >= 500 ? await circuit.recordFailure(remoteHost) : void 0;
|
|
8487
|
+
if (stateChange != null) recordCircuitBreakerSpanEvent(span, remoteHost, stateChange);
|
|
8488
|
+
} else if (isTransportDeliveryError(error)) {
|
|
8489
|
+
const stateChange = await circuit.recordFailure(remoteHost);
|
|
8490
|
+
if (stateChange != null) recordCircuitBreakerSpanEvent(span, remoteHost, stateChange);
|
|
8491
|
+
}
|
|
8492
|
+
if (!isPermanentFailure) postDecision = await circuit.beforeSend(remoteHost, message);
|
|
8493
|
+
} catch (circuitError) {
|
|
8494
|
+
circuitLogger.error("Failed to update circuit breaker state after delivery failure; falling back to normal failure handling:\n{error}", {
|
|
8495
|
+
...logData,
|
|
8496
|
+
remoteHost,
|
|
8497
|
+
error: circuitError
|
|
8498
|
+
});
|
|
8499
|
+
}
|
|
8500
|
+
span.addEvent("activitypub.delivery.failed", {
|
|
8501
|
+
"activitypub.remote.host": remoteHost,
|
|
8502
|
+
"activitypub.delivery.attempt": message.attempt,
|
|
8503
|
+
"activitypub.delivery.permanent_failure": isPermanentFailure,
|
|
8504
|
+
...error instanceof SendActivityError ? { "http.response.status_code": error.statusCode } : {}
|
|
8505
|
+
});
|
|
8506
|
+
try {
|
|
8507
|
+
await this.onOutboxError?.(error, await getActivity());
|
|
8508
|
+
} catch (handlerError) {
|
|
8509
|
+
logger.error("An unexpected error occurred in onError handler:\n{error}", {
|
|
8510
|
+
...logData,
|
|
8511
|
+
inbox: inboxHref,
|
|
8512
|
+
error: handlerError
|
|
8513
|
+
});
|
|
8514
|
+
}
|
|
8515
|
+
if (isPermanentFailure) {
|
|
8516
|
+
logger.warn("Permanent delivery failure for activity {activityId} to {inbox} ({status}); not trying the gateway again.", {
|
|
8517
|
+
...logData,
|
|
8518
|
+
inbox: inboxHref,
|
|
8519
|
+
status: error.statusCode
|
|
8520
|
+
});
|
|
8521
|
+
lastPermanent = {
|
|
8522
|
+
error,
|
|
8523
|
+
inbox
|
|
8524
|
+
};
|
|
8525
|
+
continue;
|
|
8526
|
+
}
|
|
8527
|
+
if (postDecision?.type === "drop" && circuit != null) {
|
|
8528
|
+
await drop(circuit, remoteHost, inbox, postDecision.heldSince);
|
|
8529
|
+
return;
|
|
8530
|
+
} else if (postDecision?.type === "hold") {
|
|
8531
|
+
recordCircuitBreakerHeldSpanEvent(span, remoteHost, postDecision.state);
|
|
8532
|
+
survivors.push({
|
|
8533
|
+
inbox: inboxHref,
|
|
8534
|
+
holdDelay: retryAfter == null ? postDecision.delay : maxDelay(postDecision.delay, retryAfter),
|
|
8535
|
+
circuitHeld: true,
|
|
8536
|
+
postFailure: true,
|
|
8537
|
+
notBefore: retryAt,
|
|
8538
|
+
retryAfter
|
|
8539
|
+
});
|
|
8540
|
+
} else survivors.push({
|
|
8541
|
+
inbox: inboxHref,
|
|
8542
|
+
notBefore: retryAt,
|
|
8543
|
+
retryAfter
|
|
8544
|
+
});
|
|
8545
|
+
logger.warn("Failed to send activity {activityId} to {inbox} (attempt #{attempt}); trying the next gateway if any:\n{error}", {
|
|
8546
|
+
...logData,
|
|
8547
|
+
inbox: inboxHref,
|
|
8548
|
+
error
|
|
8549
|
+
});
|
|
8550
|
+
continue;
|
|
8551
|
+
}
|
|
8552
|
+
if (circuit != null) try {
|
|
8553
|
+
const stateChange = await circuit.recordSuccess(remoteHost);
|
|
8554
|
+
if (stateChange != null) recordCircuitBreakerSpanEvent(span, remoteHost, stateChange);
|
|
8555
|
+
} catch (error) {
|
|
8556
|
+
circuitLogger.error("Failed to record successful delivery in circuit breaker state; the activity was already delivered:\n{error}", {
|
|
8557
|
+
...logData,
|
|
8558
|
+
remoteHost,
|
|
8559
|
+
error
|
|
8560
|
+
});
|
|
8561
|
+
}
|
|
8562
|
+
logger.info("Successfully sent activity {activityId} to {inbox}.", {
|
|
8563
|
+
...logData,
|
|
8564
|
+
inbox: inboxHref
|
|
8565
|
+
});
|
|
8566
|
+
return;
|
|
8567
|
+
}
|
|
8568
|
+
if (lastError != null) span.setStatus({
|
|
8569
|
+
code: _opentelemetry_api.SpanStatusCode.ERROR,
|
|
8570
|
+
message: String(lastError)
|
|
8571
|
+
});
|
|
8572
|
+
if (survivors.length < 1) {
|
|
8573
|
+
const { error, inbox } = lastPermanent;
|
|
8574
|
+
this.metrics.recordPermanentFailure(error.inbox, error.statusCode);
|
|
8575
|
+
logger.warn("Permanent delivery failure for activity {activityId} to every gateway of {portableInbox}; not retrying.", {
|
|
8576
|
+
...logData,
|
|
8577
|
+
portableInbox: message.portableInbox
|
|
8578
|
+
});
|
|
8579
|
+
if (this.outboxPermanentFailureHandler != null) {
|
|
8580
|
+
const ctx = this.#createContext(new URL(message.baseUrl), contextData, { documentLoader: this.documentLoaderFactory(this.#getLoaderOptions(message.baseUrl)) });
|
|
8581
|
+
try {
|
|
8582
|
+
await this.outboxPermanentFailureHandler(ctx, {
|
|
8583
|
+
reason: "http",
|
|
8584
|
+
inbox,
|
|
8585
|
+
activity: await getActivity(),
|
|
8586
|
+
error,
|
|
8587
|
+
statusCode: error.statusCode,
|
|
8588
|
+
actorIds: getActorIds()
|
|
8589
|
+
});
|
|
8590
|
+
} catch (handlerError) {
|
|
8591
|
+
logger.error("An unexpected error occurred in outboxPermanentFailureHandler:\n{error}", {
|
|
8592
|
+
...logData,
|
|
8593
|
+
error: handlerError
|
|
8594
|
+
});
|
|
8595
|
+
}
|
|
8596
|
+
}
|
|
8597
|
+
require_http.recordOutboxActivity(this.meterProvider, "abandoned", message.activityType);
|
|
8598
|
+
return;
|
|
8599
|
+
}
|
|
8600
|
+
const now = Temporal$2.Now.instant();
|
|
8601
|
+
const gatewayNotBefore = {};
|
|
8602
|
+
for (const survivor of survivors) if (survivor.notBefore != null && Temporal$2.Instant.compare(survivor.notBefore, now) > 0) gatewayNotBefore[survivor.inbox] = survivor.notBefore.toString();
|
|
8603
|
+
const { circuitHeld: _circuitHeld, gatewayNotBefore: _gatewayNotBefore, ...rest } = message;
|
|
8604
|
+
const nextMessage = {
|
|
8605
|
+
...rest,
|
|
8606
|
+
inbox: survivors[0].inbox,
|
|
8607
|
+
gatewayInboxes: survivors.map((s) => s.inbox),
|
|
8608
|
+
...globalThis.Object.keys(gatewayNotBefore).length < 1 ? {} : { gatewayNotBefore }
|
|
8609
|
+
};
|
|
8610
|
+
const { outboxQueue } = this;
|
|
8611
|
+
const policyDelay = this.outboxRetryPolicy({
|
|
8612
|
+
elapsedTime: Temporal$2.Instant.from(message.started).until(now),
|
|
8613
|
+
attempts: message.attempt
|
|
8614
|
+
});
|
|
8615
|
+
if (outboxQueue != null && survivors.every((s) => s.holdDelay != null) && (policyDelay != null || !survivors.some((s) => s.postFailure))) {
|
|
8616
|
+
let delay = survivors.map((s) => s.holdDelay).reduce((a, b) => Temporal$2.Duration.compare(a, b) <= 0 ? a : b);
|
|
8617
|
+
let heldMessage = nextMessage;
|
|
8618
|
+
if (survivors.some((s) => s.circuitHeld)) {
|
|
8619
|
+
const since = heldSince ?? now;
|
|
8620
|
+
if (circuit != null) delay = circuit.capHeldDelay(since, delay);
|
|
8621
|
+
heldMessage = {
|
|
8622
|
+
...nextMessage,
|
|
8623
|
+
circuitHeld: true,
|
|
8624
|
+
circuitHeldSince: since.toString()
|
|
8625
|
+
};
|
|
8626
|
+
}
|
|
8627
|
+
logger.warn("Failed to send activity {activityId} to {portableInbox}; holding it until one of its gateways can be tried again.", {
|
|
8628
|
+
...logData,
|
|
8629
|
+
portableInbox: message.portableInbox
|
|
8630
|
+
});
|
|
8631
|
+
await outboxQueue.enqueue(heldMessage, {
|
|
8632
|
+
delay: clampNegativeDelay(delay),
|
|
8633
|
+
orderingKey: message.orderingKey
|
|
8634
|
+
});
|
|
8635
|
+
this.metrics.recordQueueTaskEnqueued({
|
|
8636
|
+
role: "outbox",
|
|
8637
|
+
queue: outboxQueue,
|
|
8638
|
+
activityType: heldMessage.activityType
|
|
8639
|
+
}, heldMessage.attempt);
|
|
8640
|
+
return;
|
|
8641
|
+
}
|
|
8642
|
+
const hasRetryAfter = survivors.some((s) => s.retryAfter != null);
|
|
8643
|
+
if (outboxQueue?.nativeRetrial && !hasRetryAfter && survivors.length === message.gatewayInboxes.length && globalThis.Object.keys(gatewayNotBefore).length < 1) {
|
|
8644
|
+
logger.error("Failed to send activity {activityId} to every gateway of {portableInbox}; backend will handle retry:\n{error}", {
|
|
8645
|
+
...logData,
|
|
8646
|
+
portableInbox: message.portableInbox,
|
|
8647
|
+
error: lastError
|
|
8648
|
+
});
|
|
8649
|
+
throw lastError;
|
|
8650
|
+
}
|
|
8651
|
+
if (policyDelay == null || outboxQueue == null) {
|
|
8652
|
+
logger.error("Failed to send activity {activityId} to every gateway of {portableInbox} after {attempt} attempts; giving up:\n{error}", {
|
|
8653
|
+
...logData,
|
|
8654
|
+
portableInbox: message.portableInbox,
|
|
8655
|
+
error: lastError
|
|
8656
|
+
});
|
|
8657
|
+
require_http.recordOutboxActivity(this.meterProvider, "abandoned", message.activityType);
|
|
8658
|
+
return;
|
|
8659
|
+
}
|
|
8660
|
+
const delay = survivors.map((s) => s.holdDelay ?? s.retryAfter ?? policyDelay).reduce((a, b) => Temporal$2.Duration.compare(a, b) <= 0 ? a : b);
|
|
8661
|
+
const retryMessage = {
|
|
8662
|
+
...nextMessage,
|
|
8663
|
+
attempt: message.attempt + 1
|
|
8664
|
+
};
|
|
8665
|
+
logger.error("Failed to send activity {activityId} to every gateway of {portableInbox} (attempt #{attempt}); retry...:\n{error}", {
|
|
8666
|
+
...logData,
|
|
8667
|
+
portableInbox: message.portableInbox,
|
|
8668
|
+
error: lastError
|
|
8669
|
+
});
|
|
8670
|
+
await outboxQueue.enqueue(retryMessage, {
|
|
8671
|
+
delay: clampNegativeDelay(delay),
|
|
8672
|
+
orderingKey: message.orderingKey
|
|
8673
|
+
});
|
|
8674
|
+
this.metrics.recordQueueTaskEnqueued({
|
|
8675
|
+
role: "outbox",
|
|
8676
|
+
queue: outboxQueue,
|
|
8677
|
+
activityType: retryMessage.activityType
|
|
8678
|
+
}, retryMessage.attempt);
|
|
8679
|
+
require_http.recordOutboxActivity(this.meterProvider, "retried", retryMessage.activityType);
|
|
8680
|
+
}
|
|
8035
8681
|
async #listenInboxMessage(ctxData, message, span, onActivityType) {
|
|
8036
8682
|
const logger = (0, _logtape_logtape.getLogger)([
|
|
8037
8683
|
"fedify",
|
|
@@ -8461,13 +9107,12 @@ var FederationImpl = class extends FederationBuilderImpl {
|
|
|
8461
9107
|
activity: jsonLd
|
|
8462
9108
|
});
|
|
8463
9109
|
const promises = [];
|
|
8464
|
-
for (const inbox in inboxes) promises.push(
|
|
9110
|
+
for (const inbox in inboxes) promises.push(sendActivityToInbox(inbox, inboxes[inbox].gatewayInboxes, {
|
|
8465
9111
|
allowPrivateAddress: this.allowPrivateAddress,
|
|
8466
9112
|
keys,
|
|
8467
9113
|
activity: jsonLd,
|
|
8468
9114
|
activityId: activity.id?.href,
|
|
8469
9115
|
activityType: (0, _fedify_vocab.getTypeId)(activity).href,
|
|
8470
|
-
inbox: new URL(inbox),
|
|
8471
9116
|
sharedInbox: inboxes[inbox].sharedInbox,
|
|
8472
9117
|
headers: collectionSync == null ? void 0 : new Headers({ "Collection-Synchronization": await buildCollectionSynchronizationHeader(collectionSync, inboxes[inbox].actorIds) }),
|
|
8473
9118
|
specDeterminer: new KvSpecDeterminer(this.kv, this.kvPrefixes.httpMessageSignaturesSpec, this.firstKnock, { specTtl: this.httpMessageSignaturesSpecTtl }),
|
|
@@ -8494,8 +9139,8 @@ var FederationImpl = class extends FederationBuilderImpl {
|
|
|
8494
9139
|
_opentelemetry_api.propagation.inject(_opentelemetry_api.context.active(), carrier);
|
|
8495
9140
|
const messages = [];
|
|
8496
9141
|
for (const inbox in inboxes) {
|
|
8497
|
-
const
|
|
8498
|
-
const messageOrderingKey = orderingKey == null ? void 0 : `${orderingKey}\n${
|
|
9142
|
+
const { portableInbox, gatewayInboxes } = inboxes[inbox];
|
|
9143
|
+
const messageOrderingKey = orderingKey == null ? void 0 : `${orderingKey}\n${getOrderingDestination(inbox, portableInbox)}`;
|
|
8499
9144
|
const message = {
|
|
8500
9145
|
type: "outbox",
|
|
8501
9146
|
id: crypto.randomUUID(),
|
|
@@ -8511,6 +9156,7 @@ var FederationImpl = class extends FederationBuilderImpl {
|
|
|
8511
9156
|
attempt: 0,
|
|
8512
9157
|
headers: collectionSync == null ? {} : { "Collection-Synchronization": await buildCollectionSynchronizationHeader(collectionSync, inboxes[inbox].actorIds) },
|
|
8513
9158
|
orderingKey: messageOrderingKey,
|
|
9159
|
+
...getPortableMessageFields(portableInbox, gatewayInboxes),
|
|
8514
9160
|
traceContext: carrier
|
|
8515
9161
|
};
|
|
8516
9162
|
messages.push({
|
|
@@ -8870,10 +9516,10 @@ var FederationImpl = class extends FederationBuilderImpl {
|
|
|
8870
9516
|
onNotFound
|
|
8871
9517
|
});
|
|
8872
9518
|
case "collection": {
|
|
8873
|
-
const name = route.name
|
|
9519
|
+
const name = this.getCollectionName(route.name);
|
|
8874
9520
|
const callbacks = this.collectionCallbacks[name];
|
|
8875
9521
|
return await handleCustomCollection(request, {
|
|
8876
|
-
name,
|
|
9522
|
+
name: String(name),
|
|
8877
9523
|
context,
|
|
8878
9524
|
values: route.values,
|
|
8879
9525
|
collectionCallbacks: callbacks,
|
|
@@ -8884,10 +9530,10 @@ var FederationImpl = class extends FederationBuilderImpl {
|
|
|
8884
9530
|
});
|
|
8885
9531
|
}
|
|
8886
9532
|
case "orderedCollection": {
|
|
8887
|
-
const name = route.name
|
|
9533
|
+
const name = this.getCollectionName(route.name);
|
|
8888
9534
|
const callbacks = this.collectionCallbacks[name];
|
|
8889
9535
|
return await handleOrderedCollection(request, {
|
|
8890
|
-
name,
|
|
9536
|
+
name: String(name),
|
|
8891
9537
|
context,
|
|
8892
9538
|
values: route.values,
|
|
8893
9539
|
collectionCallbacks: callbacks,
|
|
@@ -8960,6 +9606,7 @@ var FederationImpl = class extends FederationBuilderImpl {
|
|
|
8960
9606
|
objectDispatcher: actorDispatcher == null ? void 0 : (ctx) => actorDispatcher(ctx, identifier),
|
|
8961
9607
|
authorizePredicate: authorizePredicate == null ? void 0 : (ctx) => authorizePredicate(ctx, identifier),
|
|
8962
9608
|
canonicalId: portable.canonicalId,
|
|
9609
|
+
kind: "actor",
|
|
8963
9610
|
onUnauthorized,
|
|
8964
9611
|
onNotFound
|
|
8965
9612
|
});
|
|
@@ -8980,6 +9627,7 @@ var FederationImpl = class extends FederationBuilderImpl {
|
|
|
8980
9627
|
objectDispatcher: callbacks?.dispatcher,
|
|
8981
9628
|
authorizePredicate: callbacks?.authorizePredicate,
|
|
8982
9629
|
canonicalId: portable.canonicalId,
|
|
9630
|
+
kind: "object",
|
|
8983
9631
|
onUnauthorized,
|
|
8984
9632
|
onNotFound
|
|
8985
9633
|
});
|
|
@@ -9651,8 +10299,10 @@ var ContextImpl = class ContextImpl {
|
|
|
9651
10299
|
const collectionRegex = new RegExp(`^(${["collection", "orderedCollection"].join("|")}):(.*)$`);
|
|
9652
10300
|
const match = route.name.match(collectionRegex);
|
|
9653
10301
|
if (match !== null) {
|
|
9654
|
-
const [, type
|
|
10302
|
+
const [, type] = match;
|
|
10303
|
+
const name = this.federation.getCollectionName(route.name);
|
|
9655
10304
|
const cls = this.federation.collectionTypeIds[name];
|
|
10305
|
+
if (cls == null) return null;
|
|
9656
10306
|
return {
|
|
9657
10307
|
type,
|
|
9658
10308
|
name,
|
|
@@ -9976,9 +10626,10 @@ var ContextImpl = class ContextImpl {
|
|
|
9976
10626
|
id: crypto.randomUUID(),
|
|
9977
10627
|
baseUrl: this.origin,
|
|
9978
10628
|
keys: keyJwkPairs,
|
|
9979
|
-
inboxes: globalThis.Object.fromEntries(globalThis.Object.entries(inboxes).map(([k, { actorIds, sharedInbox }]) => [k, {
|
|
10629
|
+
inboxes: globalThis.Object.fromEntries(globalThis.Object.entries(inboxes).map(([k, { actorIds, sharedInbox, portableInbox, gatewayInboxes }]) => [k, {
|
|
9980
10630
|
actorIds: [...actorIds],
|
|
9981
|
-
sharedInbox
|
|
10631
|
+
sharedInbox,
|
|
10632
|
+
...getPortableMessageFields(portableInbox, gatewayInboxes)
|
|
9982
10633
|
}])),
|
|
9983
10634
|
activity: activityJsonLd,
|
|
9984
10635
|
activityId: activity.id?.href,
|
|
@@ -10054,7 +10705,7 @@ var ContextImpl = class ContextImpl {
|
|
|
10054
10705
|
"inbox"
|
|
10055
10706
|
]);
|
|
10056
10707
|
const contextLoader = options.contextLoader ?? this.contextLoader;
|
|
10057
|
-
|
|
10708
|
+
let json = await activity.toJsonLd({ contextLoader });
|
|
10058
10709
|
const keyCache = new KvKeyCache(this.federation.kv, this.federation.kvPrefixes.publicKey, {
|
|
10059
10710
|
documentLoader: this.documentLoader,
|
|
10060
10711
|
contextLoader: this.contextLoader,
|
|
@@ -10116,6 +10767,7 @@ var ContextImpl = class ContextImpl {
|
|
|
10116
10767
|
}
|
|
10117
10768
|
logger.debug("Successfully fetched the remote activity object {activityId}; ignore the original activity and use the fetched one, which is trustworthy.");
|
|
10118
10769
|
activity = fetched;
|
|
10770
|
+
json = await fetched.toJsonLd({ contextLoader });
|
|
10119
10771
|
} else logger.debug("Object Integrity Proofs are verified.", {
|
|
10120
10772
|
recipient,
|
|
10121
10773
|
activity: json
|
|
@@ -10275,6 +10927,9 @@ var RequestContextImpl = class RequestContextImpl extends ContextImpl {
|
|
|
10275
10927
|
throw error;
|
|
10276
10928
|
}
|
|
10277
10929
|
}
|
|
10930
|
+
async isSignedByAudience(object, options = {}) {
|
|
10931
|
+
return await isInAudience(object, null) || await isInAudience(object, await this.getSignedKeyOwner(options), options);
|
|
10932
|
+
}
|
|
10278
10933
|
};
|
|
10279
10934
|
function forwardActivity(ctx, loggerCategory, forwarder, recipients, options) {
|
|
10280
10935
|
return ctx.tracerProvider.getTracer(require_http.name, require_http.version).startActiveSpan(ctx.federation.outboxQueue == null || options?.immediate ? `activitypub.${loggerCategory}` : "activitypub.fanout", {
|
|
@@ -10362,13 +11017,12 @@ async function forwardActivityInternal(ctx, loggerCategory, forwarder, recipient
|
|
|
10362
11017
|
if (options?.immediate) logger.debug("Forwarding activity immediately without queue since immediate option is set.");
|
|
10363
11018
|
else logger.debug("Forwarding activity immediately without queue since queue is not set.");
|
|
10364
11019
|
const promises = [];
|
|
10365
|
-
for (const inbox in inboxes) promises.push(
|
|
11020
|
+
for (const inbox in inboxes) promises.push(sendActivityToInbox(inbox, inboxes[inbox].gatewayInboxes, {
|
|
10366
11021
|
allowPrivateAddress: ctx.federation.allowPrivateAddress,
|
|
10367
11022
|
keys,
|
|
10368
11023
|
activity: ctx.activity,
|
|
10369
11024
|
activityId: ctx.activityId,
|
|
10370
11025
|
activityType: ctx.activityType,
|
|
10371
|
-
inbox: new URL(inbox),
|
|
10372
11026
|
sharedInbox: inboxes[inbox].sharedInbox,
|
|
10373
11027
|
meterProvider: ctx.meterProvider,
|
|
10374
11028
|
tracerProvider: ctx.tracerProvider,
|
|
@@ -10396,7 +11050,7 @@ async function forwardActivityInternal(ctx, loggerCategory, forwarder, recipient
|
|
|
10396
11050
|
const started = (/* @__PURE__ */ new Date()).toISOString();
|
|
10397
11051
|
const messages = [];
|
|
10398
11052
|
for (const inbox in inboxes) {
|
|
10399
|
-
const
|
|
11053
|
+
const { portableInbox, gatewayInboxes } = inboxes[inbox];
|
|
10400
11054
|
const message = {
|
|
10401
11055
|
type: "outbox",
|
|
10402
11056
|
id: crypto.randomUUID(),
|
|
@@ -10411,7 +11065,8 @@ async function forwardActivityInternal(ctx, loggerCategory, forwarder, recipient
|
|
|
10411
11065
|
started,
|
|
10412
11066
|
attempt: 0,
|
|
10413
11067
|
headers: {},
|
|
10414
|
-
orderingKey: orderingKey == null ? void 0 : `${orderingKey}\n${
|
|
11068
|
+
orderingKey: orderingKey == null ? void 0 : `${orderingKey}\n${getOrderingDestination(inbox, portableInbox)}`,
|
|
11069
|
+
...getPortableMessageFields(portableInbox, gatewayInboxes),
|
|
10415
11070
|
traceContext: carrier
|
|
10416
11071
|
};
|
|
10417
11072
|
messages.push({
|