@fedify/fedify 2.5.0-dev.2597 → 2.5.0-dev.2614
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-Da_V_LDj.mjs → builder-B2H8GsyI.mjs} +82 -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-BqqP38Sf.mjs → compound-proof-BavXW-ow.mjs} +2 -2
- package/dist/{context-ByflxsY9.d.cts → context-C1NqcIct.d.cts} +1061 -443
- package/dist/{context-uQUY5fQT.d.ts → context-xrqq4Hrq.d.ts} +1064 -446
- package/dist/{deno-4cGKg59R.mjs → deno-CRXki9-m.mjs} +1 -1
- package/dist/{docloader-BiHD6NSO.mjs → docloader-Ca6i6ddp.mjs} +2 -2
- 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 +3 -3
- package/dist/federation/handler.test.mjs +4 -4
- package/dist/federation/hashlink-media.test.mjs +2 -2
- package/dist/federation/idempotency.test.mjs +2 -2
- package/dist/federation/inbox-actorless.test.mjs +5 -5
- package/dist/federation/inbox-report.test.mjs +220 -5
- package/dist/federation/inbox-view.test.mjs +1 -1
- package/dist/federation/inbox.test.mjs +1 -1
- package/dist/federation/metrics.test.mjs +1 -1
- package/dist/federation/middleware.test.mjs +7 -7
- package/dist/federation/mod.cjs +1 -1
- package/dist/federation/mod.d.cts +3 -3
- package/dist/federation/mod.d.ts +4 -4
- package/dist/federation/mod.js +1 -1
- package/dist/federation/mq.test.mjs +1 -1
- package/dist/federation/outbound-body.test.d.mts +2 -0
- package/dist/federation/outbound-body.test.mjs +149 -0
- package/dist/federation/outbound-capture.test.d.mts +2 -0
- package/dist/federation/outbound-capture.test.mjs +394 -0
- package/dist/federation/outbound-dispatch.test.d.mts +2 -0
- package/dist/federation/outbound-dispatch.test.mjs +368 -0
- package/dist/federation/outbound-document.test.d.mts +2 -0
- package/dist/federation/outbound-document.test.mjs +171 -0
- package/dist/federation/outbound-provenance.test.d.mts +2 -0
- package/dist/federation/outbound-provenance.test.mjs +543 -0
- package/dist/federation/outbound-report.test.d.mts +2 -0
- package/dist/federation/outbound-report.test.mjs +5475 -0
- package/dist/federation/outbound-workers.test.d.mts +2 -0
- package/dist/federation/outbound-workers.test.mjs +64 -0
- package/dist/federation/outbound-workflow.test.d.mts +2 -0
- package/dist/federation/outbound-workflow.test.mjs +154 -0
- package/dist/federation/outgoing-proof.test.mjs +4 -4
- package/dist/federation/portable-actor.test.mjs +3 -3
- package/dist/federation/portable-chain.test.mjs +3 -3
- package/dist/federation/portable-collection.test.mjs +4 -4
- package/dist/federation/portable-delivery.test.mjs +106 -7
- package/dist/federation/portable-gateway-keys.test.mjs +5 -5
- package/dist/federation/portable-inbox.test.mjs +430 -5
- package/dist/federation/portable-parse-uri.test.mjs +1 -1
- package/dist/federation/portable-route-activity.test.mjs +2 -2
- package/dist/federation/portable.test.mjs +3 -3
- package/dist/federation/send.test.mjs +44 -3
- package/dist/federation/tasks/enqueue.test.mjs +1 -1
- package/dist/federation/tasks/tasks.test.mjs +2 -2
- package/dist/federation/temporal.test.mjs +1 -1
- package/dist/federation/webfinger.test.mjs +1 -1
- package/dist/{federation-BKJZEC0O.cjs → federation-DRl81KbJ.cjs} +2358 -405
- package/dist/{http-DQP4rw46.js → http-C2H4DT6t.js} +62 -32
- package/dist/{http-KU7_TLD9.cjs → http-DHSIMipE.cjs} +62 -32
- package/dist/{http-D5n4S1oL.mjs → http-DqOXkYbX.mjs} +64 -34
- package/dist/{inbox-CElAglrC.mjs → inbox-C4iW1g0F.mjs} +90 -3
- package/dist/{inbox-view-BTUuIIf6.mjs → inbox-view-Bu_aSwFJ.mjs} +1 -1
- package/dist/{key-DbLNT-vA.d.ts → key-dwwAg5bA.d.ts} +1 -1
- package/dist/{kv-cache-CFBwLUZr.js → kv-cache-B-x_OrBo.js} +1 -1
- package/dist/{kv-cache-DrQn-ugb.cjs → kv-cache-CHR_BVyM.cjs} +1 -1
- package/dist/{kv-cache-BV55jAE6.mjs → kv-cache-DJsxu9nI.mjs} +1 -1
- package/dist/{ld-CmAQYDWE.mjs → ld-C5JOBBFj.mjs} +2 -2
- package/dist/{ld-Inagww4R.d.ts → ld-o4LnhCYY.d.ts} +2 -2
- package/dist/{metrics-BvV60TwE.mjs → metrics-CTteRnEr.mjs} +1 -1
- package/dist/{metrics-DoRtI0qU.d.ts → metrics-DVKYY6K1.d.ts} +1 -1
- package/dist/{middleware-Di4y_M3y.js → middleware-B-tMVSUA.js} +2303 -351
- package/dist/{middleware-D02eC0j7.mjs → middleware-BkRqMGPU.mjs} +1573 -325
- package/dist/{middleware-iAJcDIM2.mjs → middleware-CPigKLnt.mjs} +1 -1
- package/dist/{mod-B7j30IQf.d.ts → mod-CQC9xlLZ.d.ts} +3 -3
- package/dist/{mod-B4kBw1-8.d.ts → mod-D22ZsiKi.d.ts} +2 -2
- package/dist/{mod-BN0TTfyQ.d.ts → mod-DO0epJGH.d.ts} +1 -1
- package/dist/{mod-DF2gbN_d.d.cts → mod-TwiQgEf3.d.cts} +1 -1
- package/dist/mod.cjs +4 -4
- package/dist/mod.d.cts +4 -4
- package/dist/mod.d.ts +8 -8
- package/dist/mod.js +4 -4
- package/dist/nodeinfo/handler.test.mjs +1 -1
- package/dist/outbound-body-CckPA5gc.mjs +83 -0
- package/dist/outbound-capture-DVgnRzlf.mjs +184 -0
- package/dist/outbound-document-BMgwLXTY.mjs +179 -0
- package/dist/{owner-C1-csjTo.mjs → owner-CKlRIkeF.mjs} +3 -3
- package/dist/{portable-collection-C0MNEEED.mjs → portable-collection-B7RBHVGl.mjs} +2 -2
- package/dist/{portable-collection-DkTLVfoM.js → portable-collection-CByyPjLc.js} +2 -2
- package/dist/{portable-collection-w6Qn-vJ3.cjs → portable-collection-CmLjeKsP.cjs} +2 -2
- package/dist/{portable-key-jtaxB7TD.mjs → portable-key-Clg0IH8C.mjs} +1 -1
- package/dist/{proof-CzWCRhnC.mjs → proof-tAb5_Dq4.mjs} +3 -3
- package/dist/{send-C9-dwpEH.mjs → send-DzfFREML.mjs} +153 -40
- 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 +38 -2
- package/dist/sig/key.test.mjs +1 -1
- package/dist/sig/ld.test.mjs +1 -1
- package/dist/sig/mitra-receive-interop.test.mjs +4 -4
- package/dist/sig/mitra-send-interop.test.mjs +3 -3
- package/dist/sig/mod.cjs +2 -2
- package/dist/sig/mod.d.ts +3 -3
- package/dist/sig/mod.js +2 -2
- package/dist/sig/owner.test.mjs +1 -1
- package/dist/sig/portable-collection.test.mjs +1 -1
- package/dist/sig/portable-interop.test.mjs +3 -3
- package/dist/sig/portable-key.test.mjs +5 -5
- package/dist/sig/proof.test.mjs +3 -3
- package/dist/sig/tootik-request-interop.test.mjs +4 -4
- package/dist/sig/tootik-send-interop.test.mjs +3 -3
- package/dist/sig/verification.test.mjs +3 -3
- package/dist/{temporal-lDCl8Zxn.mjs → temporal-D9Nt8Fm4.mjs} +1 -1
- package/dist/testing/kv.test.mjs +1 -1
- package/dist/testing/mod.d.mts +1030 -412
- package/dist/utils/docloader.test.mjs +2 -2
- package/dist/utils/kv-cache.test.mjs +1 -1
- package/dist/utils/mod.cjs +1 -1
- package/dist/utils/mod.d.ts +1 -1
- package/dist/utils/mod.js +1 -1
- package/package.json +6 -6
- package/skills/fedify/SKILL.md +60 -1
|
@@ -5,48 +5,832 @@ import { d as isPortableUri, f as isSameObjectId, i as getPortableDid, l as isPo
|
|
|
5
5
|
import { n as isPubliclyAddressedNode, t as isInAudience } from "./audience-CHQ-FlLD.mjs";
|
|
6
6
|
import { i as getEmptyAuthenticationEvidence, n as deserializeAuthentication, s as toProofPrincipal, t as createAuthenticationEvidence } from "./inbox-authentication-CgonVRkg.mjs";
|
|
7
7
|
import { a as verificationObservation, r as snapshotKey, t as observeAttempt } from "./verification-Dpfcw8ml.mjs";
|
|
8
|
-
import { n as version, t as name } from "./deno-
|
|
9
|
-
import { a as instrumentDocumentLoader, b as recordWebFingerHandle, c as recordCircuitBreakerStateChange, d as recordCollectionRequest, f as recordCollectionTotalItems, g as recordInboxActivity, h as recordFanoutRecipients, i as getRemoteHost, l as recordCollectionDispatchDuration, n as getDurationMs, o as isAbortError, r as getFederationMetrics, u as recordCollectionPageItems, v as recordOutboxActivity, x as registerQueueDepthGauge, y as recordOutboxEnqueue } from "./metrics-
|
|
8
|
+
import { n as version, t as name } from "./deno-CRXki9-m.mjs";
|
|
9
|
+
import { a as instrumentDocumentLoader, b as recordWebFingerHandle, c as recordCircuitBreakerStateChange, d as recordCollectionRequest, f as recordCollectionTotalItems, g as recordInboxActivity, h as recordFanoutRecipients, i as getRemoteHost, l as recordCollectionDispatchDuration, n as getDurationMs, o as isAbortError, r as getFederationMetrics, u as recordCollectionPageItems, v as recordOutboxActivity, x as registerQueueDepthGauge, y as recordOutboxEnqueue } from "./metrics-CTteRnEr.mjs";
|
|
10
10
|
import { t as formatAcceptSignature } from "./accept-CPVqki78.mjs";
|
|
11
11
|
import { r as getVerifiedKeyOwnerEvidence } from "./key-owner-evidence-APB6NpSk.mjs";
|
|
12
|
-
import { D as validateCryptoKey, E as importJwk, a as compactJsonLd, c as getNormalizationContextLoader, d as isClearlyMalformedContextReference, f as isInvalidUrlTypeError, g as verifyCompactJsonLdDetailed, l as hasSignature, m as signJsonLd, r as assertSafeJsonLd, s as detachSignature, t as InvalidContextReferenceError, u as hasSignatureLike, x as exportJwk, y as wrapContextLoaderForJsonLd } from "./ld-
|
|
12
|
+
import { D as validateCryptoKey, E as importJwk, a as compactJsonLd, c as getNormalizationContextLoader, d as isClearlyMalformedContextReference, f as isInvalidUrlTypeError, g as verifyCompactJsonLdDetailed, l as hasSignature, m as signJsonLd, r as assertSafeJsonLd, s as detachSignature, t as InvalidContextReferenceError, u as hasSignatureLike, x as exportJwk, y as wrapContextLoaderForJsonLd } from "./ld-C5JOBBFj.mjs";
|
|
13
13
|
import { r as normalizeOutgoingActivityJsonLd } from "./outgoing-jsonld-4IJqPYos.mjs";
|
|
14
|
-
import { a as hasProofLike, d as verifyPortableObjectProof, i as getProofSigner, l as verifyObject, p as verifyPortableObjectProofWithRoot, s as signObject, t as InvalidPortableObjectIdError, u as verifyObjectDetailed } from "./proof-
|
|
15
|
-
import { c as parseRfc9421SignatureInput, f as validateMaxSignatures, l as selectRequestSignature, m as verifyRequestDetailed, o as listRequestSignatures, p as verifyRequest } from "./http-
|
|
16
|
-
import { n as getAuthenticatedDocumentLoader } from "./docloader-
|
|
17
|
-
import { n as kvCache } from "./kv-cache-
|
|
14
|
+
import { a as hasProofLike, d as verifyPortableObjectProof, i as getProofSigner, l as verifyObject, p as verifyPortableObjectProofWithRoot, s as signObject, t as InvalidPortableObjectIdError, u as verifyObjectDetailed } from "./proof-tAb5_Dq4.mjs";
|
|
15
|
+
import { c as parseRfc9421SignatureInput, f as validateMaxSignatures, l as selectRequestSignature, m as verifyRequestDetailed, o as listRequestSignatures, p as verifyRequest } from "./http-DqOXkYbX.mjs";
|
|
16
|
+
import { n as getAuthenticatedDocumentLoader } from "./docloader-Ca6i6ddp.mjs";
|
|
17
|
+
import { n as kvCache } from "./kv-cache-DJsxu9nI.mjs";
|
|
18
|
+
import { a as isActorlessActivityAuthenticated, c as createInboxRequestSource, d as invocationProvenance, f as outboundProvenance, g as sourceLogContext, h as snapshotLocalContext, i as getInboxIdempotencyKey, l as emptyProvenance, m as serializeProvenance, n as checkActorlessActivity, o as isInboxActivityProcessed, p as restoreProvenance, s as routeActivity, t as actorsHaveSameOrigin, u as getOutboundProvenance } from "./inbox-C4iW1g0F.mjs";
|
|
19
|
+
import { t as projectSenderPayload } from "./outbound-document-BMgwLXTY.mjs";
|
|
20
|
+
import { t as captureResponseBody } from "./outbound-capture-DVgnRzlf.mjs";
|
|
21
|
+
import { a as snapshotHeaders, n as FederationBuilderImpl, t as ACTOR_ALIAS_PREFIX } from "./builder-B2H8GsyI.mjs";
|
|
18
22
|
import { d as MAX_BODY_SIZE, f as readBoundedText, t as getNodeInfo, u as BodyTooLargeError } from "./client-5SIFzW3q.mjs";
|
|
19
23
|
import { t as nodeInfoToJson } from "./types-DE_LV1JB.mjs";
|
|
20
|
-
import { n as getKeyOwner, t as doesActorOwnKey } from "./owner-
|
|
21
|
-
import { t as verifyPortableObject } from "./portable-collection-
|
|
22
|
-
import { a as sendActivityThroughGateways, i as sendActivity, n as extractInboxes, o as getOrderingDestination, t as SendActivityError } from "./send-
|
|
23
|
-
import { n as FederationBuilderImpl, t as ACTOR_ALIAS_PREFIX } from "./builder-Da_V_LDj.mjs";
|
|
24
|
+
import { n as getKeyOwner, t as doesActorOwnKey } from "./owner-CKlRIkeF.mjs";
|
|
25
|
+
import { t as verifyPortableObject } from "./portable-collection-B7RBHVGl.mjs";
|
|
26
|
+
import { a as sendActivityThroughGateways, i as sendActivity, n as extractInboxes, o as getOrderingDestination, t as SendActivityError } from "./send-DzfFREML.mjs";
|
|
24
27
|
import { t as CircuitBreaker } from "./circuit-breaker-XJ53md4S.mjs";
|
|
25
28
|
import { t as buildCollectionSynchronizationHeader } from "./collection-CnnP1UAv.mjs";
|
|
26
|
-
import { a as inspectCompoundPortableObjectApplicability, c as verifyServedPortableObjects, i as findUnsupportedCompoundProofShape, o as verifyCompoundPortableObjectProofs, r as findEmbeddedProofWithoutContext, t as containsCompoundPortableObject } from "./compound-proof-
|
|
27
|
-
import {
|
|
28
|
-
import { c as expandDocument, d as parseSerializedVerifiedActivityView, f as restoreVerifiedActivityView, i as createReleasableLoader, l as getActorPresence, n as buildVerifiedActivityView, o as deepFreeze, r as createContextSnapshot, t as ContextSnapshotMissError } from "./inbox-view-BTUuIIf6.mjs";
|
|
29
|
+
import { a as inspectCompoundPortableObjectApplicability, c as verifyServedPortableObjects, i as findUnsupportedCompoundProofShape, o as verifyCompoundPortableObjectProofs, r as findEmbeddedProofWithoutContext, t as containsCompoundPortableObject } from "./compound-proof-BavXW-ow.mjs";
|
|
30
|
+
import { c as expandDocument, d as parseSerializedVerifiedActivityView, f as restoreVerifiedActivityView, i as createReleasableLoader, l as getActorPresence, n as buildVerifiedActivityView, o as deepFreeze, r as createContextSnapshot, t as ContextSnapshotMissError } from "./inbox-view-Bu_aSwFJ.mjs";
|
|
29
31
|
import { t as KvKeyCache } from "./keycache-N2opu4-3.mjs";
|
|
30
32
|
import { t as acceptsJsonLd } from "./negotiation-CCocWT_N.mjs";
|
|
31
|
-
import { t as hasMalformedKnownTemporalLiteral } from "./temporal-
|
|
33
|
+
import { t as hasMalformedKnownTemporalLiteral } from "./temporal-D9Nt8Fm4.mjs";
|
|
32
34
|
import { t as createExponentialBackoffPolicy } from "./retry-zX5xw5e0.mjs";
|
|
33
35
|
import { t as TaskCodec } from "./codec-C6rTrpIp.mjs";
|
|
34
36
|
import { getLogger, withContext } from "@logtape/logtape";
|
|
35
37
|
import { RouterError } from "@fedify/uri-template";
|
|
36
38
|
import { Activity, Collection, CollectionPage, CryptographicKey, Link, Multikey, Object as Object$1, OrderedCollection, OrderedCollectionPage, Tombstone, getTypeId, isActor, lookupObject, lookupObserver, traverseCollection } from "@fedify/vocab";
|
|
37
39
|
import { lookupWebFinger } from "@fedify/webfinger";
|
|
38
|
-
import { SpanKind, SpanStatusCode, context, metrics, propagation, trace } from "@opentelemetry/api";
|
|
40
|
+
import { SpanKind, SpanStatusCode, context, isSpanContextValid, metrics, propagation, trace } from "@opentelemetry/api";
|
|
39
41
|
import { FetchError, canonicalizePortableUri, decodeMultibase, formatIri, fromCompatibleEf61Id, getDocumentLoader, getFe34Origin, isGatewayUrl, parseDigestMultibase, parseHashlink, parseIri, resolveDocumentLoaderTimeout, toCompatibleEf61Id } from "@fedify/vocab-runtime";
|
|
40
42
|
import { isCompatibleEf61Iri } from "@fedify/vocab-runtime/internal/portable-dereference";
|
|
41
43
|
import { uniq } from "es-toolkit";
|
|
42
44
|
import { ATTR_HTTP_REQUEST_HEADER, ATTR_HTTP_REQUEST_METHOD, ATTR_HTTP_RESPONSE_HEADER, ATTR_HTTP_RESPONSE_STATUS_CODE, ATTR_URL_FULL } from "@opentelemetry/semantic-conventions";
|
|
45
|
+
import { encodeHex } from "byte-encodings/hex";
|
|
43
46
|
import jsonld from "@fedify/vocab-runtime/jsonld";
|
|
44
47
|
import { DataPointType, MeterProvider as MeterProvider$1, MetricReader } from "@opentelemetry/sdk-metrics";
|
|
45
48
|
import { domainToASCII } from "node:url";
|
|
49
|
+
//#region src/federation/outbound-observation.ts
|
|
50
|
+
function snapshotDeliveryIdentity(identity) {
|
|
51
|
+
if (identity == null || typeof identity.deliveryId !== "string" || identity.deliveryId.length < 1 || typeof identity.destinationInbox !== "string" || identity.destinationInbox.length < 1 || identity.broadcast == null || !(identity.broadcast.type === "unknown" || identity.broadcast.type === "known" && typeof identity.broadcast.id === "string" && identity.broadcast.id.length > 0) || !(identity.portableInbox == null || typeof identity.portableInbox === "string")) throw new TypeError("Invalid outbound delivery identity in queue metadata.");
|
|
52
|
+
return Object.freeze({
|
|
53
|
+
broadcast: Object.freeze(identity.broadcast.type === "known" ? {
|
|
54
|
+
type: "known",
|
|
55
|
+
id: identity.broadcast.id
|
|
56
|
+
} : { type: "unknown" }),
|
|
57
|
+
deliveryId: identity.deliveryId,
|
|
58
|
+
destinationInbox: identity.destinationInbox,
|
|
59
|
+
...identity.portableInbox == null ? {} : { portableInbox: identity.portableInbox }
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
async function legacyFanoutIdentity(taskId, inbox, portableInbox) {
|
|
63
|
+
const bytes = new TextEncoder().encode(JSON.stringify([
|
|
64
|
+
"fedify-legacy-fanout-delivery",
|
|
65
|
+
taskId,
|
|
66
|
+
inbox
|
|
67
|
+
]));
|
|
68
|
+
const digest = await crypto.subtle.digest("SHA-256", bytes);
|
|
69
|
+
return {
|
|
70
|
+
broadcast: { type: "unknown" },
|
|
71
|
+
deliveryId: `legacy-fanout:${encodeHex(new Uint8Array(digest))}`,
|
|
72
|
+
destinationInbox: inbox,
|
|
73
|
+
...portableInbox == null ? {} : { portableInbox }
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
async function notifyObserver(context, callback, event, unit) {
|
|
77
|
+
if (callback == null) return;
|
|
78
|
+
try {
|
|
79
|
+
await withContext(sourceLogContext(event.provenance), () => callback(context, event));
|
|
80
|
+
} catch (error) {
|
|
81
|
+
logObserverFailure(unit, error);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
function logObserverFailure(unit, error) {
|
|
85
|
+
try {
|
|
86
|
+
getLogger([
|
|
87
|
+
"fedify",
|
|
88
|
+
"federation",
|
|
89
|
+
"outbox"
|
|
90
|
+
]).error("An unexpected error occurred in outbound {unit} observer:\n{error}", {
|
|
91
|
+
unit,
|
|
92
|
+
error
|
|
93
|
+
});
|
|
94
|
+
} catch {}
|
|
95
|
+
}
|
|
96
|
+
function warnInvalidDeliveryIdentity(messageId, error) {
|
|
97
|
+
try {
|
|
98
|
+
getLogger([
|
|
99
|
+
"fedify",
|
|
100
|
+
"federation",
|
|
101
|
+
"outbox"
|
|
102
|
+
]).warn("Ignoring invalid outbound delivery identity in queue message {messageId}: {error}", {
|
|
103
|
+
messageId,
|
|
104
|
+
error
|
|
105
|
+
});
|
|
106
|
+
} catch {}
|
|
107
|
+
}
|
|
108
|
+
function snapshotTrace(span, parentSpanContext) {
|
|
109
|
+
const spanContext = span.spanContext();
|
|
110
|
+
if (!isSpanContextValid(spanContext) || spanContext.isRemote || spanContext.traceId === parentSpanContext?.traceId && spanContext.spanId === parentSpanContext.spanId) return void 0;
|
|
111
|
+
return Object.freeze({
|
|
112
|
+
traceId: spanContext.traceId,
|
|
113
|
+
spanId: spanContext.spanId
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
/** State owned by one concrete send, never persisted in queue metadata. */
|
|
117
|
+
var OutboundAttemptObservation = class {
|
|
118
|
+
inboxUrl;
|
|
119
|
+
report;
|
|
120
|
+
reportExchange;
|
|
121
|
+
headerPolicies;
|
|
122
|
+
bodyCapture;
|
|
123
|
+
senderPayload;
|
|
124
|
+
#attemptId = crypto.randomUUID();
|
|
125
|
+
#failures = [];
|
|
126
|
+
#response = Object.freeze({ type: "not-observed" });
|
|
127
|
+
#finalResponse;
|
|
128
|
+
#responses = /* @__PURE__ */ new WeakMap();
|
|
129
|
+
#trace;
|
|
130
|
+
#finished = false;
|
|
131
|
+
#exchangeCount = 0;
|
|
132
|
+
#senderPayload;
|
|
133
|
+
constructor(inboxUrl, report, reportExchange = void 0, headerPolicies = {}, bodyCapture = void 0, senderPayload = false) {
|
|
134
|
+
this.inboxUrl = inboxUrl;
|
|
135
|
+
this.report = report;
|
|
136
|
+
this.reportExchange = reportExchange;
|
|
137
|
+
this.headerPolicies = headerPolicies;
|
|
138
|
+
this.bodyCapture = bodyCapture;
|
|
139
|
+
this.senderPayload = senderPayload;
|
|
140
|
+
this.#senderPayload = Object.freeze({
|
|
141
|
+
stage: "sender-payload",
|
|
142
|
+
type: "unavailable",
|
|
143
|
+
reason: senderPayload ? "not-materialized" : "disabled"
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
snapshotSenderPayload(input) {
|
|
147
|
+
if (this.senderPayload) this.#senderPayload = projectSenderPayload(input);
|
|
148
|
+
}
|
|
149
|
+
setSpan(span, parentSpanContext) {
|
|
150
|
+
this.#trace = snapshotTrace(span, parentSpanContext);
|
|
151
|
+
}
|
|
152
|
+
async fetch(request, submit) {
|
|
153
|
+
const exchangeIndex = this.#exchangeCount++;
|
|
154
|
+
const snapshot = Object.freeze({
|
|
155
|
+
url: request.url,
|
|
156
|
+
method: request.method,
|
|
157
|
+
...this.headerPolicies.requestHeaders == null ? {} : { headers: snapshotHeaders(request.headers, this.headerPolicies.requestHeaders) }
|
|
158
|
+
});
|
|
159
|
+
let response;
|
|
160
|
+
try {
|
|
161
|
+
response = await submit();
|
|
162
|
+
} catch (error) {
|
|
163
|
+
await this.#exchange(exchangeIndex, snapshot, {
|
|
164
|
+
type: "failed",
|
|
165
|
+
error
|
|
166
|
+
});
|
|
167
|
+
throw error;
|
|
168
|
+
}
|
|
169
|
+
const metadata = Object.freeze({
|
|
170
|
+
status: response.status,
|
|
171
|
+
statusText: response.statusText,
|
|
172
|
+
...response.url === "" ? {} : { url: response.url }
|
|
173
|
+
});
|
|
174
|
+
const observed = Object.freeze({
|
|
175
|
+
exchangeIndex,
|
|
176
|
+
metadata
|
|
177
|
+
});
|
|
178
|
+
this.#responses.set(response, observed);
|
|
179
|
+
this.#response = Object.freeze({
|
|
180
|
+
type: "undetermined",
|
|
181
|
+
lastObserved: observed
|
|
182
|
+
});
|
|
183
|
+
const responseSnapshot = Object.freeze({
|
|
184
|
+
...metadata,
|
|
185
|
+
...this.headerPolicies.responseHeaders == null ? {} : { headers: snapshotHeaders(response.headers, this.headerPolicies.responseHeaders) }
|
|
186
|
+
});
|
|
187
|
+
const bodyCapture = this.bodyCapture == null ? void 0 : await captureResponseBody(response, this.bodyCapture, (body, signal) => this.bodyCapture.onBody({
|
|
188
|
+
attemptId: this.#attemptId,
|
|
189
|
+
inboxUrl: this.inboxUrl,
|
|
190
|
+
exchangeIndex,
|
|
191
|
+
observedAt: Temporal.Now.instant().epochMilliseconds,
|
|
192
|
+
request: snapshot,
|
|
193
|
+
response: responseSnapshot,
|
|
194
|
+
body,
|
|
195
|
+
signal
|
|
196
|
+
}));
|
|
197
|
+
if (bodyCapture?.type === "failed" && bodyCapture.phase === "observer") logObserverFailure("response-body", bodyCapture.error);
|
|
198
|
+
await this.#exchange(exchangeIndex, snapshot, {
|
|
199
|
+
type: "response",
|
|
200
|
+
response: responseSnapshot
|
|
201
|
+
}, bodyCapture);
|
|
202
|
+
return response;
|
|
203
|
+
}
|
|
204
|
+
#exchange(exchangeIndex, request, outcome, bodyCapture) {
|
|
205
|
+
return this.reportExchange?.({
|
|
206
|
+
attemptId: this.#attemptId,
|
|
207
|
+
inboxUrl: this.inboxUrl,
|
|
208
|
+
exchangeIndex,
|
|
209
|
+
observedAt: Temporal.Now.instant().epochMilliseconds,
|
|
210
|
+
request,
|
|
211
|
+
outcome: Object.freeze(outcome),
|
|
212
|
+
...bodyCapture == null ? {} : { bodyCapture }
|
|
213
|
+
}) ?? Promise.resolve();
|
|
214
|
+
}
|
|
215
|
+
response(response, disposition) {
|
|
216
|
+
const observed = this.#responses.get(response);
|
|
217
|
+
if (observed == null) return;
|
|
218
|
+
if (disposition === "final") {
|
|
219
|
+
this.#finalResponse = Object.freeze({
|
|
220
|
+
type: "final",
|
|
221
|
+
...observed
|
|
222
|
+
});
|
|
223
|
+
this.#response = this.#finalResponse;
|
|
224
|
+
} else this.#response = Object.freeze({
|
|
225
|
+
type: "intermediate-only",
|
|
226
|
+
lastObserved: observed
|
|
227
|
+
});
|
|
228
|
+
}
|
|
229
|
+
failure(failure) {
|
|
230
|
+
this.#failures.push(Object.freeze(failure));
|
|
231
|
+
}
|
|
232
|
+
unknown(error) {
|
|
233
|
+
if (!this.#failures.some((failure) => failure.error === error || failure.phase !== "cleanup")) this.failure({
|
|
234
|
+
kind: "unknown",
|
|
235
|
+
phase: "unknown",
|
|
236
|
+
error
|
|
237
|
+
});
|
|
238
|
+
}
|
|
239
|
+
succeeded() {
|
|
240
|
+
if (this.#finished) return Promise.resolve();
|
|
241
|
+
if (this.#finalResponse == null) throw new Error("A successful outbound attempt must have an observed final response.");
|
|
242
|
+
return this.#finish({
|
|
243
|
+
type: "succeeded",
|
|
244
|
+
response: this.#finalResponse
|
|
245
|
+
});
|
|
246
|
+
}
|
|
247
|
+
failed(error) {
|
|
248
|
+
let failure;
|
|
249
|
+
let diagnostics;
|
|
250
|
+
if (this.#response.type === "final" && (this.#response.metadata.status < 200 || this.#response.metadata.status >= 300)) {
|
|
251
|
+
failure = this.#failures.find((failure) => failure.kind === "http") ?? Object.freeze({
|
|
252
|
+
kind: "http",
|
|
253
|
+
phase: "response"
|
|
254
|
+
});
|
|
255
|
+
diagnostics = this.#failures.filter((failure) => failure.kind !== "http");
|
|
256
|
+
} else {
|
|
257
|
+
failure = this.#failures.find((failure) => failure.phase !== "cleanup") ?? this.#failures[0] ?? Object.freeze({
|
|
258
|
+
kind: "unknown",
|
|
259
|
+
phase: "unknown",
|
|
260
|
+
error
|
|
261
|
+
});
|
|
262
|
+
diagnostics = this.#failures.filter((item) => item !== failure);
|
|
263
|
+
}
|
|
264
|
+
return this.#finish({
|
|
265
|
+
type: "failed",
|
|
266
|
+
failure,
|
|
267
|
+
error,
|
|
268
|
+
response: this.#response,
|
|
269
|
+
...diagnostics.length === 0 ? {} : { diagnostics: Object.freeze(diagnostics) }
|
|
270
|
+
});
|
|
271
|
+
}
|
|
272
|
+
#finish(outcome) {
|
|
273
|
+
if (this.#finished) return Promise.resolve();
|
|
274
|
+
this.#finished = true;
|
|
275
|
+
return this.report({
|
|
276
|
+
attemptId: this.#attemptId,
|
|
277
|
+
inboxUrl: this.inboxUrl,
|
|
278
|
+
httpExchangeCount: this.#exchangeCount,
|
|
279
|
+
senderPayload: this.#senderPayload,
|
|
280
|
+
observedAt: Temporal.Now.instant().epochMilliseconds,
|
|
281
|
+
...this.#trace == null ? {} : { trace: this.#trace },
|
|
282
|
+
outcome: Object.freeze(outcome)
|
|
283
|
+
});
|
|
284
|
+
}
|
|
285
|
+
};
|
|
286
|
+
function snapshotOutboxIdentity(message) {
|
|
287
|
+
if (message.outbound != null) try {
|
|
288
|
+
return snapshotDeliveryIdentity(message.outbound);
|
|
289
|
+
} catch (error) {
|
|
290
|
+
warnInvalidDeliveryIdentity(message.id, error);
|
|
291
|
+
}
|
|
292
|
+
return Object.freeze({
|
|
293
|
+
broadcast: Object.freeze({ type: "unknown" }),
|
|
294
|
+
deliveryId: message.id,
|
|
295
|
+
...message.portableInbox == null ? {} : { portableInbox: message.portableInbox }
|
|
296
|
+
});
|
|
297
|
+
}
|
|
298
|
+
/** Per-execution state, independent of concrete attempt/exchange callbacks. */
|
|
299
|
+
var OutboundWorkerObservation = class {
|
|
300
|
+
context;
|
|
301
|
+
callback;
|
|
302
|
+
provenance;
|
|
303
|
+
#attemptsStarted = 0;
|
|
304
|
+
#actions;
|
|
305
|
+
#trace;
|
|
306
|
+
#preparationFailure;
|
|
307
|
+
#preparationFailures = [];
|
|
308
|
+
#queue;
|
|
309
|
+
#delivery;
|
|
310
|
+
#role;
|
|
311
|
+
#currentContext;
|
|
312
|
+
constructor(context, callback, message, provenance) {
|
|
313
|
+
this.context = context;
|
|
314
|
+
this.callback = callback;
|
|
315
|
+
this.provenance = provenance;
|
|
316
|
+
this.#role = message.type;
|
|
317
|
+
this.#queue = Object.freeze({
|
|
318
|
+
queueMessageId: message.id,
|
|
319
|
+
...message.type === "outbox" ? { queueAttempt: message.attempt } : {}
|
|
320
|
+
});
|
|
321
|
+
if (message.type === "outbox") this.#delivery = snapshotOutboxIdentity(message);
|
|
322
|
+
}
|
|
323
|
+
attemptStarted = () => {
|
|
324
|
+
this.#attemptsStarted++;
|
|
325
|
+
};
|
|
326
|
+
enableActions() {
|
|
327
|
+
this.#actions ??= [];
|
|
328
|
+
}
|
|
329
|
+
directAttemptStarted(inboxUrl, identity) {
|
|
330
|
+
this.attemptStarted();
|
|
331
|
+
this.#actions?.push(Object.freeze({
|
|
332
|
+
type: "handoff",
|
|
333
|
+
action: "direct",
|
|
334
|
+
inboxUrls: Object.freeze([inboxUrl]),
|
|
335
|
+
...identity == null ? {} : { delivery: snapshotDeliveryIdentity(identity) }
|
|
336
|
+
}));
|
|
337
|
+
}
|
|
338
|
+
async enqueueChildren(messages, enqueue) {
|
|
339
|
+
if (this.#actions == null) return await enqueue();
|
|
340
|
+
const facts = messages.map((message) => ({
|
|
341
|
+
action: "enqueue",
|
|
342
|
+
queue: Object.freeze({
|
|
343
|
+
queueMessageId: message.id,
|
|
344
|
+
queueAttempt: message.attempt
|
|
345
|
+
}),
|
|
346
|
+
inboxUrls: Object.freeze(message.gatewayInboxes != null && message.gatewayInboxes.length > 1 ? [...message.gatewayInboxes] : [message.inbox]),
|
|
347
|
+
...message.outbound == null ? {} : { delivery: snapshotDeliveryIdentity(message.outbound) }
|
|
348
|
+
}));
|
|
349
|
+
try {
|
|
350
|
+
await enqueue();
|
|
351
|
+
} catch (error) {
|
|
352
|
+
for (const fact of facts) this.#actions.push(Object.freeze({
|
|
353
|
+
type: "action-failed",
|
|
354
|
+
...fact,
|
|
355
|
+
acceptance: "unknown",
|
|
356
|
+
error
|
|
357
|
+
}));
|
|
358
|
+
throw error;
|
|
359
|
+
}
|
|
360
|
+
for (const fact of facts) this.#actions.push(Object.freeze({
|
|
361
|
+
type: "scheduled",
|
|
362
|
+
...fact
|
|
363
|
+
}));
|
|
364
|
+
}
|
|
365
|
+
async enqueue(action, message, delay, enqueue, preparationFailures) {
|
|
366
|
+
if (this.#actions == null) return await enqueue();
|
|
367
|
+
const facts = {
|
|
368
|
+
action,
|
|
369
|
+
queue: Object.freeze({
|
|
370
|
+
queueMessageId: message.id,
|
|
371
|
+
queueAttempt: message.attempt
|
|
372
|
+
}),
|
|
373
|
+
inboxUrls: Object.freeze(message.gatewayInboxes != null && message.gatewayInboxes.length > 1 ? [...message.gatewayInboxes] : [message.inbox]),
|
|
374
|
+
delay: delay.toString(),
|
|
375
|
+
...preparationFailures == null || preparationFailures.length === 0 ? {} : { preparationFailures: Object.freeze([...preparationFailures]) },
|
|
376
|
+
...message.gatewayNotBefore == null ? {} : { notBefore: Object.freeze({ ...message.gatewayNotBefore }) }
|
|
377
|
+
};
|
|
378
|
+
try {
|
|
379
|
+
await enqueue();
|
|
380
|
+
} catch (error) {
|
|
381
|
+
this.#actions.push(Object.freeze({
|
|
382
|
+
type: "action-failed",
|
|
383
|
+
...facts,
|
|
384
|
+
acceptance: "unknown",
|
|
385
|
+
error
|
|
386
|
+
}));
|
|
387
|
+
throw error;
|
|
388
|
+
}
|
|
389
|
+
this.#actions.push(Object.freeze({
|
|
390
|
+
type: "scheduled",
|
|
391
|
+
...facts
|
|
392
|
+
}));
|
|
393
|
+
}
|
|
394
|
+
abandon(reason, inboxUrls, preparationFailures) {
|
|
395
|
+
this.#actions?.push(Object.freeze({
|
|
396
|
+
type: "abandoned",
|
|
397
|
+
reason,
|
|
398
|
+
inboxUrls: Object.freeze([...inboxUrls]),
|
|
399
|
+
...preparationFailures == null || preparationFailures.length === 0 ? {} : { preparationFailures: Object.freeze([...preparationFailures]) }
|
|
400
|
+
}));
|
|
401
|
+
}
|
|
402
|
+
dropFailure(inboxUrls, error) {
|
|
403
|
+
this.#actions?.push(Object.freeze({
|
|
404
|
+
type: "action-failed",
|
|
405
|
+
action: "drop",
|
|
406
|
+
inboxUrls: Object.freeze([...inboxUrls]),
|
|
407
|
+
error
|
|
408
|
+
}));
|
|
409
|
+
}
|
|
410
|
+
async drop(inboxUrls, operation) {
|
|
411
|
+
try {
|
|
412
|
+
await operation();
|
|
413
|
+
} catch (error) {
|
|
414
|
+
this.dropFailure(inboxUrls, error);
|
|
415
|
+
throw error;
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
getContext() {
|
|
419
|
+
return this.#currentContext ??= this.context();
|
|
420
|
+
}
|
|
421
|
+
useContext(context) {
|
|
422
|
+
this.#currentContext = context;
|
|
423
|
+
}
|
|
424
|
+
setSpan(span, parentSpanContext) {
|
|
425
|
+
this.#trace = snapshotTrace(span, parentSpanContext);
|
|
426
|
+
}
|
|
427
|
+
async prepare(phase, operation) {
|
|
428
|
+
try {
|
|
429
|
+
const result = await operation();
|
|
430
|
+
this.#preparationFailure = void 0;
|
|
431
|
+
return result;
|
|
432
|
+
} catch (error) {
|
|
433
|
+
this.#preparationFailure = Object.freeze({
|
|
434
|
+
kind: "local",
|
|
435
|
+
phase,
|
|
436
|
+
error
|
|
437
|
+
});
|
|
438
|
+
throw error;
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
recordPreparationFailure(error, inboxUrl) {
|
|
442
|
+
const failure = this.#preparationFailure;
|
|
443
|
+
if (failure == null || !Object.is(failure.error, error)) return [];
|
|
444
|
+
const diagnostic = Object.freeze({
|
|
445
|
+
inboxUrl,
|
|
446
|
+
failure
|
|
447
|
+
});
|
|
448
|
+
this.#preparationFailures.push(diagnostic);
|
|
449
|
+
return [diagnostic];
|
|
450
|
+
}
|
|
451
|
+
async run(operation) {
|
|
452
|
+
try {
|
|
453
|
+
await operation();
|
|
454
|
+
} catch (error) {
|
|
455
|
+
await this.#notify(this.#preparationFailure == null || !Object.is(this.#preparationFailure.error, error) ? {
|
|
456
|
+
type: "failed",
|
|
457
|
+
error
|
|
458
|
+
} : {
|
|
459
|
+
type: "preparation-failed",
|
|
460
|
+
failure: this.#preparationFailure,
|
|
461
|
+
error
|
|
462
|
+
});
|
|
463
|
+
throw error;
|
|
464
|
+
}
|
|
465
|
+
await this.#notify({ type: "completed" });
|
|
466
|
+
}
|
|
467
|
+
async #notify(result) {
|
|
468
|
+
const observedAt = Temporal.Now.instant().epochMilliseconds;
|
|
469
|
+
try {
|
|
470
|
+
const context = this.getContext();
|
|
471
|
+
await notifyObserver(context, this.callback, Object.freeze({
|
|
472
|
+
role: this.#role,
|
|
473
|
+
queue: this.#queue,
|
|
474
|
+
...this.#delivery == null ? {} : { delivery: this.#delivery },
|
|
475
|
+
attemptsStarted: this.#attemptsStarted,
|
|
476
|
+
actions: Object.freeze([...this.#actions ?? []]),
|
|
477
|
+
...this.#preparationFailures.length === 0 ? {} : { preparationFailures: Object.freeze([...this.#preparationFailures]) },
|
|
478
|
+
observedAt,
|
|
479
|
+
...this.#trace == null ? {} : { trace: this.#trace },
|
|
480
|
+
provenance: this.provenance,
|
|
481
|
+
local: snapshotLocalContext(context),
|
|
482
|
+
result: Object.freeze(result)
|
|
483
|
+
}), "worker");
|
|
484
|
+
} catch (error) {
|
|
485
|
+
logObserverFailure("worker", error);
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
};
|
|
489
|
+
/** Selected callback and restored grouping, shared by concrete gateway attempts. */
|
|
490
|
+
var OutboundAttemptObserver = class OutboundAttemptObserver {
|
|
491
|
+
context;
|
|
492
|
+
callback;
|
|
493
|
+
identity;
|
|
494
|
+
provenance;
|
|
495
|
+
onHttpExchange;
|
|
496
|
+
headerPolicies;
|
|
497
|
+
bodyCapture;
|
|
498
|
+
#queue;
|
|
499
|
+
constructor(context, callback, identity, provenance, queue, onHttpExchange = void 0, headerPolicies = {}, bodyCapture = void 0) {
|
|
500
|
+
this.context = context;
|
|
501
|
+
this.callback = callback;
|
|
502
|
+
this.identity = identity;
|
|
503
|
+
this.provenance = provenance;
|
|
504
|
+
this.onHttpExchange = onHttpExchange;
|
|
505
|
+
this.headerPolicies = headerPolicies;
|
|
506
|
+
this.bodyCapture = bodyCapture;
|
|
507
|
+
this.#queue = queue == null ? void 0 : Object.freeze({ ...queue });
|
|
508
|
+
}
|
|
509
|
+
static resumeOutbox(context, callback, message, provenance, onHttpExchange, headerPolicies = {}, bodyCapture) {
|
|
510
|
+
if (callback == null && onHttpExchange == null && bodyCapture == null) return;
|
|
511
|
+
const identity = snapshotOutboxIdentity(message);
|
|
512
|
+
return new OutboundAttemptObserver(context, callback, identity, provenance, {
|
|
513
|
+
queueMessageId: message.id,
|
|
514
|
+
queueAttempt: message.attempt
|
|
515
|
+
}, onHttpExchange, headerPolicies, bodyCapture);
|
|
516
|
+
}
|
|
517
|
+
startAttempt(inboxUrl) {
|
|
518
|
+
if (this.callback == null && this.onHttpExchange == null && this.bodyCapture == null) return void 0;
|
|
519
|
+
const context = this.context();
|
|
520
|
+
return new OutboundAttemptObservation(inboxUrl, (facts) => notifyObserver(context, this.callback, Object.freeze({
|
|
521
|
+
...this.identity,
|
|
522
|
+
...facts,
|
|
523
|
+
...this.#queue == null ? {} : { queue: this.#queue },
|
|
524
|
+
provenance: this.provenance,
|
|
525
|
+
local: snapshotLocalContext(context)
|
|
526
|
+
}), "attempt"), (facts) => notifyObserver(context, this.onHttpExchange, Object.freeze({
|
|
527
|
+
...this.identity,
|
|
528
|
+
...facts,
|
|
529
|
+
...this.#queue == null ? {} : { queue: this.#queue },
|
|
530
|
+
provenance: this.provenance,
|
|
531
|
+
local: snapshotLocalContext(context)
|
|
532
|
+
}), "http-exchange"), this.onHttpExchange == null && this.bodyCapture == null ? {} : this.headerPolicies, this.bodyCapture == null ? void 0 : {
|
|
533
|
+
...this.bodyCapture,
|
|
534
|
+
onBody: (event) => withContext(sourceLogContext(this.provenance), () => this.bodyCapture.onBody(context, Object.freeze({
|
|
535
|
+
...this.identity,
|
|
536
|
+
...event,
|
|
537
|
+
...this.#queue == null ? {} : { queue: this.#queue },
|
|
538
|
+
provenance: this.provenance,
|
|
539
|
+
local: snapshotLocalContext(context)
|
|
540
|
+
})))
|
|
541
|
+
}, this.callback != null && this.headerPolicies.senderPayload === true);
|
|
542
|
+
}
|
|
543
|
+
};
|
|
544
|
+
/** One producer's destination, without callbacks or live inputs in queues. */
|
|
545
|
+
var OutboundDeliveryObservation = class {
|
|
546
|
+
context;
|
|
547
|
+
observer;
|
|
548
|
+
provenance;
|
|
549
|
+
trace;
|
|
550
|
+
#finished = false;
|
|
551
|
+
#reference;
|
|
552
|
+
#plan;
|
|
553
|
+
#attempts;
|
|
554
|
+
constructor(context, observer, identity, destination, provenance, trace, onAttempt, onHttpExchange, queue, headerPolicies = {}, bodyCapture) {
|
|
555
|
+
this.context = context;
|
|
556
|
+
this.observer = observer;
|
|
557
|
+
this.provenance = provenance;
|
|
558
|
+
this.trace = trace;
|
|
559
|
+
this.#reference = snapshotDeliveryIdentity(identity);
|
|
560
|
+
this.#attempts = new OutboundAttemptObserver(() => this.context, onAttempt, this.#reference, this.provenance, queue, onHttpExchange, headerPolicies, bodyCapture);
|
|
561
|
+
this.#plan = Object.freeze({
|
|
562
|
+
actorIds: Object.freeze(Array.from(destination.actorIds)),
|
|
563
|
+
sharedInbox: destination.sharedInbox,
|
|
564
|
+
mode: destination.mode ?? (destination.portableInbox == null ? "ordinary" : "portable-fallback")
|
|
565
|
+
});
|
|
566
|
+
}
|
|
567
|
+
startAttempt(inboxUrl) {
|
|
568
|
+
return this.#attempts.startAttempt(inboxUrl);
|
|
569
|
+
}
|
|
570
|
+
get identity() {
|
|
571
|
+
return this.#reference;
|
|
572
|
+
}
|
|
573
|
+
planned() {
|
|
574
|
+
return notifyObserver(this.context, this.observer, Object.freeze({
|
|
575
|
+
...this.trace == null ? {} : { trace: this.trace },
|
|
576
|
+
observedAt: Temporal.Now.instant().epochMilliseconds,
|
|
577
|
+
provenance: this.provenance,
|
|
578
|
+
local: snapshotLocalContext(this.context),
|
|
579
|
+
...this.#reference,
|
|
580
|
+
...this.#plan,
|
|
581
|
+
stage: "planned"
|
|
582
|
+
}), "delivery");
|
|
583
|
+
}
|
|
584
|
+
handoff(result) {
|
|
585
|
+
if (this.#finished) return Promise.resolve();
|
|
586
|
+
this.#finished = true;
|
|
587
|
+
return notifyObserver(this.context, this.observer, Object.freeze({
|
|
588
|
+
...this.trace == null ? {} : { trace: this.trace },
|
|
589
|
+
observedAt: Temporal.Now.instant().epochMilliseconds,
|
|
590
|
+
provenance: this.provenance,
|
|
591
|
+
local: snapshotLocalContext(this.context),
|
|
592
|
+
...this.#reference,
|
|
593
|
+
stage: "handoff",
|
|
594
|
+
result: Object.freeze(result)
|
|
595
|
+
}), "delivery");
|
|
596
|
+
}
|
|
597
|
+
failed(error) {
|
|
598
|
+
if (this.#finished) return Promise.resolve();
|
|
599
|
+
this.#finished = true;
|
|
600
|
+
return notifyObserver(this.context, this.observer, Object.freeze({
|
|
601
|
+
...this.trace == null ? {} : { trace: this.trace },
|
|
602
|
+
observedAt: Temporal.Now.instant().epochMilliseconds,
|
|
603
|
+
provenance: this.provenance,
|
|
604
|
+
local: snapshotLocalContext(this.context),
|
|
605
|
+
...this.#reference,
|
|
606
|
+
stage: "preparation-failed",
|
|
607
|
+
failure: Object.freeze({
|
|
608
|
+
kind: "local",
|
|
609
|
+
phase: "request-preparation",
|
|
610
|
+
error
|
|
611
|
+
})
|
|
612
|
+
}), "delivery");
|
|
613
|
+
}
|
|
614
|
+
};
|
|
615
|
+
/** State owned by one outgoing invocation, never serialized into queues. */
|
|
616
|
+
var OutboundBroadcastObservation = class OutboundBroadcastObservation {
|
|
617
|
+
context;
|
|
618
|
+
operation;
|
|
619
|
+
provenance;
|
|
620
|
+
queue;
|
|
621
|
+
#broadcast;
|
|
622
|
+
#finished = false;
|
|
623
|
+
#phase = "unknown";
|
|
624
|
+
#planning = "not-started";
|
|
625
|
+
#destinationCount = 0;
|
|
626
|
+
#trace;
|
|
627
|
+
#activity = Object.freeze({});
|
|
628
|
+
#onBroadcast;
|
|
629
|
+
#onDelivery;
|
|
630
|
+
#onAttempt;
|
|
631
|
+
#onHttpExchange;
|
|
632
|
+
#headerPolicies;
|
|
633
|
+
#bodyCapture;
|
|
634
|
+
#deliveries = /* @__PURE__ */ new Map();
|
|
635
|
+
constructor(context, observers, operation, provenance = getOutboundProvenance(context), broadcast = {
|
|
636
|
+
type: "known",
|
|
637
|
+
id: crypto.randomUUID()
|
|
638
|
+
}, queue = void 0) {
|
|
639
|
+
this.context = context;
|
|
640
|
+
this.operation = operation;
|
|
641
|
+
this.provenance = provenance;
|
|
642
|
+
this.queue = queue;
|
|
643
|
+
this.#broadcast = broadcast;
|
|
644
|
+
this.#onBroadcast = observers.onBroadcast;
|
|
645
|
+
this.#onDelivery = observers.onDelivery;
|
|
646
|
+
this.#onAttempt = observers.onAttempt;
|
|
647
|
+
this.#onHttpExchange = observers.onHttpExchange;
|
|
648
|
+
this.#bodyCapture = observers.responseBodyCapture;
|
|
649
|
+
this.#headerPolicies = {
|
|
650
|
+
requestHeaders: observers.requestHeaders,
|
|
651
|
+
responseHeaders: observers.responseHeaders,
|
|
652
|
+
senderPayload: observers.senderPayload
|
|
653
|
+
};
|
|
654
|
+
}
|
|
655
|
+
/** Continue an existing fanout; do not emit a new broadcast invocation. */
|
|
656
|
+
static async resumeFanout(context, observers, message, span, parentSpanContext) {
|
|
657
|
+
if (observers.onDelivery == null && observers.onAttempt == null && observers.onHttpExchange == null && observers.responseBodyCapture == null && observers.onWorker == null && !Object.values(message.inboxes).some((entry) => entry.outbound != null)) return;
|
|
658
|
+
const observation = new OutboundBroadcastObservation(context, {
|
|
659
|
+
onDelivery: observers.onDelivery,
|
|
660
|
+
onAttempt: observers.onAttempt,
|
|
661
|
+
onHttpExchange: observers.onHttpExchange,
|
|
662
|
+
requestHeaders: observers.requestHeaders,
|
|
663
|
+
responseHeaders: observers.responseHeaders,
|
|
664
|
+
responseBodyCapture: observers.responseBodyCapture,
|
|
665
|
+
senderPayload: observers.senderPayload
|
|
666
|
+
}, "send", getOutboundProvenance(context), { type: "unknown" }, { queueMessageId: message.id });
|
|
667
|
+
if (span != null) observation.setSpan(span, parentSpanContext);
|
|
668
|
+
observation.#finished = true;
|
|
669
|
+
observation.planned(Object.keys(message.inboxes).length);
|
|
670
|
+
for (const [inbox, destination] of Object.entries(message.inboxes)) {
|
|
671
|
+
let identity;
|
|
672
|
+
if (destination.outbound != null) try {
|
|
673
|
+
const snapshot = snapshotDeliveryIdentity(destination.outbound);
|
|
674
|
+
if (snapshot.destinationInbox !== inbox) throw new TypeError("Outbound delivery identity does not match its fanout destination.");
|
|
675
|
+
identity = snapshot;
|
|
676
|
+
} catch (error) {
|
|
677
|
+
warnInvalidDeliveryIdentity(message.id, error);
|
|
678
|
+
}
|
|
679
|
+
identity ??= await legacyFanoutIdentity(message.id, inbox, destination.portableInbox);
|
|
680
|
+
observation.#deliveries.set(inbox, observation.#createDelivery(inbox, destination, identity));
|
|
681
|
+
}
|
|
682
|
+
return observation;
|
|
683
|
+
}
|
|
684
|
+
#createDelivery(inbox, destination, identity) {
|
|
685
|
+
return new OutboundDeliveryObservation(this.context, this.#onDelivery, identity ?? {
|
|
686
|
+
broadcast: this.#broadcast,
|
|
687
|
+
deliveryId: crypto.randomUUID(),
|
|
688
|
+
destinationInbox: inbox,
|
|
689
|
+
...destination.portableInbox == null ? {} : { portableInbox: destination.portableInbox }
|
|
690
|
+
}, destination, this.provenance, this.#trace, this.#onAttempt, this.#onHttpExchange, this.queue, this.#headerPolicies, this.#bodyCapture);
|
|
691
|
+
}
|
|
692
|
+
continuation(inbox) {
|
|
693
|
+
const delivery = this.delivery(inbox);
|
|
694
|
+
return delivery == null ? void 0 : { outbound: delivery.identity };
|
|
695
|
+
}
|
|
696
|
+
async planDestinations(inboxes) {
|
|
697
|
+
this.planned(Object.keys(inboxes).length);
|
|
698
|
+
for (const [inbox, destination] of Object.entries(inboxes)) if (!this.#deliveries.has(inbox)) this.#deliveries.set(inbox, this.#createDelivery(inbox, destination));
|
|
699
|
+
for (const delivery of this.#deliveries.values()) await delivery.planned();
|
|
700
|
+
}
|
|
701
|
+
delivery(inbox) {
|
|
702
|
+
return this.#deliveries.get(inbox);
|
|
703
|
+
}
|
|
704
|
+
async enqueue(entries, queue, enqueue) {
|
|
705
|
+
let result;
|
|
706
|
+
try {
|
|
707
|
+
result = await enqueue();
|
|
708
|
+
} catch (error) {
|
|
709
|
+
await Promise.all(entries.map(({ inbox }) => this.delivery(inbox)?.handoff({
|
|
710
|
+
type: "enqueue-failed",
|
|
711
|
+
acceptance: "unknown",
|
|
712
|
+
error
|
|
713
|
+
})));
|
|
714
|
+
throw error;
|
|
715
|
+
}
|
|
716
|
+
await Promise.all(entries.map(({ inbox, queueMessageId }) => this.delivery(inbox)?.handoff({
|
|
717
|
+
type: "enqueued",
|
|
718
|
+
queue,
|
|
719
|
+
queueMessageId
|
|
720
|
+
})));
|
|
721
|
+
return result;
|
|
722
|
+
}
|
|
723
|
+
/** Attach only the span supplied by this execution, never an ambient fallback. */
|
|
724
|
+
setSpan(span, parentSpanContext) {
|
|
725
|
+
this.#trace = snapshotTrace(span, parentSpanContext);
|
|
726
|
+
}
|
|
727
|
+
setPhase(phase) {
|
|
728
|
+
this.#phase = phase;
|
|
729
|
+
if (phase === "destination-planning") this.#planning = "incomplete";
|
|
730
|
+
}
|
|
731
|
+
/** Replace the Activity facts captured by the producer path. */
|
|
732
|
+
setActivity(reference) {
|
|
733
|
+
this.#activity = Object.freeze({ ...reference });
|
|
734
|
+
}
|
|
735
|
+
planned(destinationCount) {
|
|
736
|
+
this.#planning = "complete";
|
|
737
|
+
this.#destinationCount = destinationCount;
|
|
738
|
+
}
|
|
739
|
+
async planDestination(inbox, destination) {
|
|
740
|
+
this.#destinationCount++;
|
|
741
|
+
const delivery = this.#createDelivery(inbox, destination);
|
|
742
|
+
this.#deliveries.set(inbox, delivery);
|
|
743
|
+
await delivery.planned();
|
|
744
|
+
}
|
|
745
|
+
prepared(preparedFor) {
|
|
746
|
+
return this.#finish(Object.freeze({
|
|
747
|
+
type: "prepared",
|
|
748
|
+
preparedFor,
|
|
749
|
+
destinationCount: this.#destinationCount
|
|
750
|
+
}));
|
|
751
|
+
}
|
|
752
|
+
skipped(reason = "no-destinations") {
|
|
753
|
+
return this.#finish(Object.freeze({
|
|
754
|
+
type: "skipped",
|
|
755
|
+
reason
|
|
756
|
+
}));
|
|
757
|
+
}
|
|
758
|
+
async run(send) {
|
|
759
|
+
if (this.#broadcast.type === "known") await this.#notify(Object.freeze({
|
|
760
|
+
broadcastId: this.#broadcast.id,
|
|
761
|
+
operation: this.operation,
|
|
762
|
+
stage: "started",
|
|
763
|
+
activity: this.#activity
|
|
764
|
+
}));
|
|
765
|
+
try {
|
|
766
|
+
return await send(this, this.provenance);
|
|
767
|
+
} catch (error) {
|
|
768
|
+
await this.failed(error);
|
|
769
|
+
throw error;
|
|
770
|
+
}
|
|
771
|
+
}
|
|
772
|
+
failed(error) {
|
|
773
|
+
const failure = {
|
|
774
|
+
kind: this.#phase === "unknown" ? "unknown" : "local",
|
|
775
|
+
phase: this.#phase,
|
|
776
|
+
error
|
|
777
|
+
};
|
|
778
|
+
return this.#finish(Object.freeze({
|
|
779
|
+
type: "failed",
|
|
780
|
+
failure: Object.freeze(failure),
|
|
781
|
+
planning: this.#planning,
|
|
782
|
+
plannedDestinationCount: this.#destinationCount
|
|
783
|
+
}));
|
|
784
|
+
}
|
|
785
|
+
async #finish(result) {
|
|
786
|
+
if (this.#finished) return;
|
|
787
|
+
this.#finished = true;
|
|
788
|
+
if (this.#broadcast.type === "known") await this.#notify(Object.freeze({
|
|
789
|
+
broadcastId: this.#broadcast.id,
|
|
790
|
+
operation: this.operation,
|
|
791
|
+
stage: "preparation-finished",
|
|
792
|
+
result,
|
|
793
|
+
activity: this.#activity
|
|
794
|
+
}));
|
|
795
|
+
}
|
|
796
|
+
#notify(event) {
|
|
797
|
+
return notifyObserver(this.context, this.#onBroadcast, Object.freeze({
|
|
798
|
+
...event,
|
|
799
|
+
...this.#trace == null ? {} : { trace: this.#trace },
|
|
800
|
+
observedAt: Temporal.Now.instant().epochMilliseconds,
|
|
801
|
+
provenance: this.provenance,
|
|
802
|
+
local: snapshotLocalContext(this.context)
|
|
803
|
+
}), "broadcast");
|
|
804
|
+
}
|
|
805
|
+
};
|
|
806
|
+
/** Observe preparation without changing the unobserved send's execution path. */
|
|
807
|
+
function observeOutboundBroadcast(context, observers, send, operation = "send", dispatchId) {
|
|
808
|
+
let provenance;
|
|
809
|
+
let invalid;
|
|
810
|
+
try {
|
|
811
|
+
provenance = invocationProvenance(context, dispatchId);
|
|
812
|
+
} catch (error) {
|
|
813
|
+
provenance = invocationProvenance(context, void 0);
|
|
814
|
+
invalid = { error };
|
|
815
|
+
}
|
|
816
|
+
const execute = async (observation) => {
|
|
817
|
+
if (invalid != null) {
|
|
818
|
+
observation?.setPhase("validation");
|
|
819
|
+
throw invalid.error;
|
|
820
|
+
}
|
|
821
|
+
return await send(observation, provenance);
|
|
822
|
+
};
|
|
823
|
+
return withContext(sourceLogContext(provenance), () => {
|
|
824
|
+
if (observers.onBroadcast == null && observers.onDelivery == null && observers.onAttempt == null && observers.onHttpExchange == null && observers.responseBodyCapture == null) return execute();
|
|
825
|
+
return new OutboundBroadcastObservation(context, observers, operation, provenance).run(execute);
|
|
826
|
+
});
|
|
827
|
+
}
|
|
828
|
+
//#endregion
|
|
46
829
|
//#region src/federation/inbox-observation.ts
|
|
47
830
|
/** Internal state owned by the ingress boundary, never serialized into queues. */
|
|
48
831
|
var InboxObservation = class {
|
|
49
832
|
inbox;
|
|
833
|
+
source;
|
|
50
834
|
payload = { status: "unavailable" };
|
|
51
835
|
activity = null;
|
|
52
836
|
authentication = { status: "notDetermined" };
|
|
@@ -69,8 +853,9 @@ var InboxObservation = class {
|
|
|
69
853
|
}
|
|
70
854
|
}
|
|
71
855
|
};
|
|
72
|
-
constructor(inbox) {
|
|
856
|
+
constructor(inbox, source = void 0) {
|
|
73
857
|
this.inbox = inbox;
|
|
858
|
+
this.source = source;
|
|
74
859
|
}
|
|
75
860
|
/** Run an ingress operation and project telemetry; fetch owns completion. */
|
|
76
861
|
async run(operation, span) {
|
|
@@ -126,7 +911,18 @@ var InboxObservation = class {
|
|
|
126
911
|
}
|
|
127
912
|
async finish(context, handler, outcome) {
|
|
128
913
|
if (handler == null) return;
|
|
914
|
+
const signal = context.request.signal;
|
|
915
|
+
const requestCancellation = signal.aborted ? Object.freeze({
|
|
916
|
+
type: "aborted",
|
|
917
|
+
reason: signal.reason
|
|
918
|
+
}) : Object.freeze({ type: "not-aborted" });
|
|
129
919
|
const report = {
|
|
920
|
+
...this.source == null ? {} : {
|
|
921
|
+
inboxRequestId: this.source.inboxRequestId,
|
|
922
|
+
requestId: this.source.requestId,
|
|
923
|
+
local: Object.freeze({ ...this.source.context })
|
|
924
|
+
},
|
|
925
|
+
requestCancellation,
|
|
130
926
|
inbox: this.inbox,
|
|
131
927
|
payload: this.payload,
|
|
132
928
|
activity: this.activity,
|
|
@@ -135,7 +931,7 @@ var InboxObservation = class {
|
|
|
135
931
|
outcome
|
|
136
932
|
};
|
|
137
933
|
try {
|
|
138
|
-
await handler(context, report);
|
|
934
|
+
await withContext(this.source == null ? {} : { inboxRequestId: this.source.inboxRequestId }, () => handler(context, report));
|
|
139
935
|
} catch (error) {
|
|
140
936
|
try {
|
|
141
937
|
getLogger([
|
|
@@ -949,9 +1745,10 @@ function coversDelivery(signature) {
|
|
|
949
1745
|
* other gateways, that gateway has the activity already, so it is claimed
|
|
950
1746
|
* without forwarding the activity to it. See {@link identifySendingGateway}.
|
|
951
1747
|
* @param parameters The parameters.
|
|
1748
|
+
* @param observation The in-process producer observation, if registered.
|
|
952
1749
|
* @returns The target inbox URLs that the activity was handed off to.
|
|
953
1750
|
*/
|
|
954
|
-
async function forwardPortableInboxActivity(parameters) {
|
|
1751
|
+
async function forwardPortableInboxActivity(parameters, observation) {
|
|
955
1752
|
const logger = getLogger([
|
|
956
1753
|
"fedify",
|
|
957
1754
|
"federation",
|
|
@@ -960,8 +1757,16 @@ async function forwardPortableInboxActivity(parameters) {
|
|
|
960
1757
|
const { recipient, activityId, excludedOrigins, kv, kvPrefix, outboxQueue } = parameters;
|
|
961
1758
|
const options = parameters.options ?? resolvePortableInboxForwardingOptions();
|
|
962
1759
|
const { maxTargets, ttl } = options;
|
|
963
|
-
if (kv.cas == null
|
|
1760
|
+
if (kv.cas == null) {
|
|
1761
|
+
if (observation != null) await observation.skipped("forwarding-unavailable");
|
|
1762
|
+
return [];
|
|
1763
|
+
}
|
|
1764
|
+
if (maxTargets < 1) {
|
|
1765
|
+
if (observation != null) await observation.skipped("forwarding-disabled");
|
|
1766
|
+
return [];
|
|
1767
|
+
}
|
|
964
1768
|
const deadline = Date.now() + options.deadline.total("millisecond");
|
|
1769
|
+
observation?.setPhase("destination-planning");
|
|
965
1770
|
const canonicalActivityId = getCanonicalPortableId(activityId) ?? activityId.href;
|
|
966
1771
|
const claimKey = (gateway) => [
|
|
967
1772
|
...kvPrefix,
|
|
@@ -989,15 +1794,27 @@ async function forwardPortableInboxActivity(parameters) {
|
|
|
989
1794
|
targets.length = maxTargets;
|
|
990
1795
|
}
|
|
991
1796
|
const claimed = [];
|
|
992
|
-
for (const { gateway, inbox } of targets) if (await kv.cas(claimKey(gateway), void 0, true, { ttl }))
|
|
993
|
-
|
|
1797
|
+
for (const { gateway, inbox } of targets) if (await kv.cas(claimKey(gateway), void 0, true, { ttl })) {
|
|
1798
|
+
claimed.push(inbox);
|
|
1799
|
+
if (observation != null) await observation.planDestination(inbox.href, {
|
|
1800
|
+
actorIds: [recipient.actorId.href],
|
|
1801
|
+
sharedInbox: false,
|
|
1802
|
+
portableInbox: recipient.canonicalInboxId,
|
|
1803
|
+
mode: "portable-replication"
|
|
1804
|
+
});
|
|
1805
|
+
}
|
|
1806
|
+
observation?.planned(claimed.length);
|
|
1807
|
+
if (claimed.length < 1) {
|
|
1808
|
+
if (observation != null) await observation.skipped("no-forwarding-targets");
|
|
1809
|
+
return [];
|
|
1810
|
+
}
|
|
994
1811
|
logger.debug("Forwarding activity {activityId} from the portable inbox {inbox} to other gateways:\n{targets}", {
|
|
995
1812
|
activityId: activityId.href,
|
|
996
1813
|
inbox: recipient.canonicalInboxId,
|
|
997
1814
|
targets: claimed.map((t) => t.href)
|
|
998
1815
|
});
|
|
999
|
-
if (outboxQueue == null) await forwardImmediately(parameters, claimed, deadline);
|
|
1000
|
-
else await enqueueForwarding(parameters, outboxQueue, claimed);
|
|
1816
|
+
if (outboxQueue == null) await forwardImmediately(parameters, claimed, deadline, observation);
|
|
1817
|
+
else await enqueueForwarding(parameters, outboxQueue, claimed, observation);
|
|
1001
1818
|
return claimed;
|
|
1002
1819
|
}
|
|
1003
1820
|
/**
|
|
@@ -1140,7 +1957,7 @@ async function resolveForwardingKey({ getKeys, activityId }) {
|
|
|
1140
1957
|
}
|
|
1141
1958
|
return key;
|
|
1142
1959
|
}
|
|
1143
|
-
async function forwardImmediately(parameters, inboxes, deadlineAt) {
|
|
1960
|
+
async function forwardImmediately(parameters, inboxes, deadlineAt, observation) {
|
|
1144
1961
|
const { activity, activityId, activityType, allowPrivateAddress, specDeterminer, meterProvider, tracerProvider } = parameters;
|
|
1145
1962
|
const logger = getLogger([
|
|
1146
1963
|
"fedify",
|
|
@@ -1148,23 +1965,50 @@ async function forwardImmediately(parameters, inboxes, deadlineAt) {
|
|
|
1148
1965
|
"inbox"
|
|
1149
1966
|
]);
|
|
1150
1967
|
const deadline = Math.max(0, deadlineAt - Date.now());
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1968
|
+
observation?.setPhase("key-resolution");
|
|
1969
|
+
const forwarding = (async () => {
|
|
1970
|
+
try {
|
|
1971
|
+
const key = await resolveForwardingKey(parameters);
|
|
1972
|
+
observation?.setPhase("request-preparation");
|
|
1973
|
+
const sending = [];
|
|
1974
|
+
for (const inbox of inboxes) {
|
|
1975
|
+
const delivery = observation?.delivery(inbox.href);
|
|
1976
|
+
let pending;
|
|
1977
|
+
try {
|
|
1978
|
+
pending = sendActivity({
|
|
1979
|
+
observation: delivery,
|
|
1980
|
+
activity,
|
|
1981
|
+
activityId: activityId.href,
|
|
1982
|
+
activityType,
|
|
1983
|
+
keys: key == null ? [] : [key],
|
|
1984
|
+
inbox,
|
|
1985
|
+
allowPrivateAddress,
|
|
1986
|
+
specDeterminer,
|
|
1987
|
+
meterProvider,
|
|
1988
|
+
tracerProvider
|
|
1989
|
+
});
|
|
1990
|
+
} catch (error) {
|
|
1991
|
+
if (delivery != null) await delivery.failed(error);
|
|
1992
|
+
throw error;
|
|
1993
|
+
}
|
|
1994
|
+
const settled = pending.catch((error) => {
|
|
1995
|
+
logger.error("Failed to forward activity {activityId} to {inbox}; it will not be forwarded to the inbox again:\n{error}", {
|
|
1996
|
+
activityId: activityId.href,
|
|
1997
|
+
inbox: inbox.href,
|
|
1998
|
+
error
|
|
1999
|
+
});
|
|
2000
|
+
});
|
|
2001
|
+
if (observation != null) settled.catch(() => {});
|
|
2002
|
+
sending.push(settled);
|
|
2003
|
+
if (delivery != null) await delivery.handoff({ type: "direct" });
|
|
2004
|
+
}
|
|
2005
|
+
if (observation != null) await observation.prepared("delivery");
|
|
2006
|
+
await Promise.all(sending);
|
|
2007
|
+
} catch (error) {
|
|
2008
|
+
if (observation != null) await observation.failed(error);
|
|
2009
|
+
throw error;
|
|
2010
|
+
}
|
|
2011
|
+
})();
|
|
1168
2012
|
let timer;
|
|
1169
2013
|
const timedOut = await Promise.race([forwarding.then(() => false), new Promise((resolve) => {
|
|
1170
2014
|
timer = setTimeout(() => resolve(true), deadline);
|
|
@@ -1175,13 +2019,14 @@ async function forwardImmediately(parameters, inboxes, deadlineAt) {
|
|
|
1175
2019
|
deadline
|
|
1176
2020
|
});
|
|
1177
2021
|
}
|
|
1178
|
-
async function enqueueForwarding(parameters, outboxQueue, inboxes) {
|
|
2022
|
+
async function enqueueForwarding(parameters, outboxQueue, inboxes, observation) {
|
|
1179
2023
|
const { activity, activityId, activityType, baseUrl, startQueue, meterProvider } = parameters;
|
|
1180
2024
|
const logger = getLogger([
|
|
1181
2025
|
"fedify",
|
|
1182
2026
|
"federation",
|
|
1183
2027
|
"inbox"
|
|
1184
2028
|
]);
|
|
2029
|
+
observation?.setPhase("key-resolution");
|
|
1185
2030
|
const key = await resolveForwardingKey(parameters);
|
|
1186
2031
|
const keys = [];
|
|
1187
2032
|
if (key != null) try {
|
|
@@ -1196,28 +2041,43 @@ async function enqueueForwarding(parameters, outboxQueue, inboxes) {
|
|
|
1196
2041
|
error
|
|
1197
2042
|
});
|
|
1198
2043
|
}
|
|
2044
|
+
observation?.setPhase("request-preparation");
|
|
1199
2045
|
startQueue?.();
|
|
1200
2046
|
const started = (/* @__PURE__ */ new Date()).toISOString();
|
|
1201
2047
|
const traceContext = {};
|
|
1202
2048
|
propagation.inject(context.active(), traceContext);
|
|
2049
|
+
let preparedCount = 0;
|
|
1203
2050
|
await Promise.all(inboxes.map(async (inbox) => {
|
|
1204
|
-
const
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
2051
|
+
const delivery = observation?.delivery(inbox.href);
|
|
2052
|
+
let message;
|
|
2053
|
+
try {
|
|
2054
|
+
message = {
|
|
2055
|
+
type: "outbox",
|
|
2056
|
+
...serializeProvenance(parameters.provenance),
|
|
2057
|
+
id: crypto.randomUUID(),
|
|
2058
|
+
baseUrl,
|
|
2059
|
+
keys,
|
|
2060
|
+
activity,
|
|
2061
|
+
activityId: activityId.href,
|
|
2062
|
+
activityType,
|
|
2063
|
+
inbox: inbox.href,
|
|
2064
|
+
...observation?.continuation(inbox.href),
|
|
2065
|
+
sharedInbox: false,
|
|
2066
|
+
started,
|
|
2067
|
+
attempt: 0,
|
|
2068
|
+
headers: {},
|
|
2069
|
+
traceContext
|
|
2070
|
+
};
|
|
2071
|
+
} catch (error) {
|
|
2072
|
+
if (delivery != null) await delivery.failed(error);
|
|
2073
|
+
throw error;
|
|
2074
|
+
}
|
|
2075
|
+
if (observation != null && ++preparedCount === inboxes.length) await observation.prepared("delivery");
|
|
1219
2076
|
try {
|
|
1220
|
-
await outboxQueue.enqueue(message)
|
|
2077
|
+
await (observation == null ? outboxQueue.enqueue(message) : observation.enqueue([{
|
|
2078
|
+
inbox: inbox.href,
|
|
2079
|
+
queueMessageId: message.id
|
|
2080
|
+
}], "outbox", () => outboxQueue.enqueue(message)));
|
|
1221
2081
|
} catch (error) {
|
|
1222
2082
|
logger.error("Failed to enqueue activity {activityId} to forward to {inbox}; it will not be forwarded to the inbox again:\n{error}", {
|
|
1223
2083
|
activityId: activityId.href,
|
|
@@ -5016,41 +5876,70 @@ var FederationImpl = class extends FederationBuilderImpl {
|
|
|
5016
5876
|
}
|
|
5017
5877
|
await Promise.all(promises);
|
|
5018
5878
|
}
|
|
5019
|
-
processQueuedTask(contextData, message) {
|
|
5879
|
+
async processQueuedTask(contextData, message) {
|
|
5020
5880
|
const extractedContext = propagation.extract(context.active(), message.traceContext);
|
|
5021
|
-
|
|
5881
|
+
let provenance = emptyProvenance;
|
|
5882
|
+
if (message.type !== "task") try {
|
|
5883
|
+
provenance = restoreProvenance(message.provenance);
|
|
5884
|
+
} catch (error) {
|
|
5885
|
+
try {
|
|
5886
|
+
getLogger([
|
|
5887
|
+
"fedify",
|
|
5888
|
+
"federation",
|
|
5889
|
+
"queue"
|
|
5890
|
+
]).warn("Ignoring invalid provenance in queue message {messageId}: {error}", {
|
|
5891
|
+
messageId: message.id,
|
|
5892
|
+
error
|
|
5893
|
+
});
|
|
5894
|
+
} catch {}
|
|
5895
|
+
}
|
|
5896
|
+
if (message.type !== "task" && message.provenance !== void 0) message = {
|
|
5897
|
+
...message,
|
|
5898
|
+
provenance
|
|
5899
|
+
};
|
|
5900
|
+
return await withContext({
|
|
5901
|
+
messageId: message.id,
|
|
5902
|
+
inboxRequestId: void 0,
|
|
5903
|
+
...sourceLogContext(provenance)
|
|
5904
|
+
}, async () => {
|
|
5022
5905
|
if (message.type === "fanout") {
|
|
5906
|
+
const observers = this.outboundObservers;
|
|
5907
|
+
const worker = this.#createOutboundWorkerObservation(contextData, message, provenance, observers.onWorker);
|
|
5023
5908
|
const common = {
|
|
5024
5909
|
role: "fanout",
|
|
5025
5910
|
queue: this.fanoutQueue,
|
|
5026
5911
|
activityType: message.activityType
|
|
5027
5912
|
};
|
|
5028
|
-
|
|
5913
|
+
const execute = () => this.#runWorkerSpan("activitypub.fanout", { "activitypub.activity.type": message.activityType }, extractedContext, this.#instrumentWorkerBody(common, async (span) => {
|
|
5029
5914
|
if (message.activityId != null) span.setAttribute("activitypub.activity.id", message.activityId);
|
|
5030
|
-
await this.#listenFanoutMessage(contextData, message);
|
|
5915
|
+
await this.#listenFanoutMessage(contextData, message, provenance, span, trace.getSpanContext(extractedContext), observers, worker);
|
|
5031
5916
|
return { outcome: "completed" };
|
|
5032
|
-
}, classifyAbortableError));
|
|
5917
|
+
}, classifyAbortableError), worker);
|
|
5918
|
+
await (worker == null ? execute() : worker.run(execute));
|
|
5033
5919
|
} else if (message.type === "outbox") {
|
|
5920
|
+
const observers = this.outboundObservers;
|
|
5921
|
+
const worker = this.#createOutboundWorkerObservation(contextData, message, provenance, observers.onWorker);
|
|
5034
5922
|
const common = {
|
|
5035
5923
|
role: "outbox",
|
|
5036
5924
|
queue: this.outboxQueue,
|
|
5037
5925
|
activityType: message.activityType
|
|
5038
5926
|
};
|
|
5039
|
-
|
|
5927
|
+
const execute = () => this.#runWorkerSpan("activitypub.outbox", {
|
|
5040
5928
|
"activitypub.activity.type": message.activityType,
|
|
5041
5929
|
"activitypub.activity.retries": message.attempt
|
|
5042
5930
|
}, extractedContext, this.#instrumentWorkerBody(common, async (span) => {
|
|
5043
5931
|
if (message.activityId != null) span.setAttribute("activitypub.activity.id", message.activityId);
|
|
5044
|
-
await this.#listenOutboxMessage(contextData, message, span);
|
|
5932
|
+
await this.#listenOutboxMessage(contextData, message, span, provenance, observers, worker);
|
|
5045
5933
|
return { outcome: "completed" };
|
|
5046
|
-
}, classifyAbortableError));
|
|
5934
|
+
}, classifyAbortableError), worker);
|
|
5935
|
+
await (worker == null ? execute() : worker.run(execute));
|
|
5047
5936
|
} else if (message.type === "inbox") {
|
|
5048
5937
|
const common = {
|
|
5049
5938
|
role: "inbox",
|
|
5050
5939
|
queue: this.inboxQueue
|
|
5051
5940
|
};
|
|
5052
5941
|
await this.#runWorkerSpan("activitypub.inbox", { "activitypub.shared_inbox": message.identifier == null }, extractedContext, this.#instrumentWorkerBody(common, async (span) => {
|
|
5053
|
-
await this.#listenInboxMessage(contextData, message, span, (activityType) => {
|
|
5942
|
+
await this.#listenInboxMessage(contextData, message, span, provenance, (activityType) => {
|
|
5054
5943
|
common.activityType = activityType;
|
|
5055
5944
|
});
|
|
5056
5945
|
return { outcome: "completed" };
|
|
@@ -5069,6 +5958,22 @@ var FederationImpl = class extends FederationBuilderImpl {
|
|
|
5069
5958
|
}
|
|
5070
5959
|
});
|
|
5071
5960
|
}
|
|
5961
|
+
#createOutboundWorkerObservation(contextData, message, provenance, callback) {
|
|
5962
|
+
if (callback == null) return void 0;
|
|
5963
|
+
let documentLoader;
|
|
5964
|
+
let contextLoader;
|
|
5965
|
+
return new OutboundWorkerObservation(() => {
|
|
5966
|
+
const loaderOptions = this.#getLoaderOptions(message.baseUrl);
|
|
5967
|
+
return new ContextImpl({
|
|
5968
|
+
[outboundProvenance]: provenance,
|
|
5969
|
+
federation: this,
|
|
5970
|
+
url: new URL("/", message.baseUrl),
|
|
5971
|
+
data: contextData,
|
|
5972
|
+
documentLoader: (...args) => (documentLoader ??= this.documentLoaderFactory(loaderOptions))(...args),
|
|
5973
|
+
contextLoader: (...args) => (contextLoader ??= this.contextLoaderFactory(loaderOptions))(...args)
|
|
5974
|
+
});
|
|
5975
|
+
}, callback, message, provenance);
|
|
5976
|
+
}
|
|
5072
5977
|
/**
|
|
5073
5978
|
* Opens a CONSUMER span for a queue worker and scopes the extracted trace
|
|
5074
5979
|
* context around its body. The body—typically built by
|
|
@@ -5080,11 +5985,12 @@ var FederationImpl = class extends FederationBuilderImpl {
|
|
|
5080
5985
|
* @param instrumentedBody Builds the context-scoped worker callback for the
|
|
5081
5986
|
* opened span.
|
|
5082
5987
|
*/
|
|
5083
|
-
async #runWorkerSpan(spanName, spanAttributes, extractedContext, instrumentedBody) {
|
|
5988
|
+
async #runWorkerSpan(spanName, spanAttributes, extractedContext, instrumentedBody, worker) {
|
|
5084
5989
|
await this._getTracer().startActiveSpan(spanName, {
|
|
5085
5990
|
kind: SpanKind.CONSUMER,
|
|
5086
5991
|
attributes: spanAttributes
|
|
5087
5992
|
}, extractedContext, async (span) => {
|
|
5993
|
+
worker?.setSpan(span, trace.getSpanContext(extractedContext));
|
|
5088
5994
|
const spanCtx = span.spanContext();
|
|
5089
5995
|
return await withContext({
|
|
5090
5996
|
traceId: spanCtx.traceId,
|
|
@@ -5127,7 +6033,8 @@ var FederationImpl = class extends FederationBuilderImpl {
|
|
|
5127
6033
|
span.end();
|
|
5128
6034
|
}
|
|
5129
6035
|
};
|
|
5130
|
-
async #listenFanoutMessage(data, message) {
|
|
6036
|
+
async #listenFanoutMessage(data, message, provenance, span, parentSpanContext, observers, worker) {
|
|
6037
|
+
worker?.enableActions();
|
|
5131
6038
|
getLogger([
|
|
5132
6039
|
"fedify",
|
|
5133
6040
|
"federation",
|
|
@@ -5136,11 +6043,12 @@ var FederationImpl = class extends FederationBuilderImpl {
|
|
|
5136
6043
|
activityId: message.activityId,
|
|
5137
6044
|
inboxes: globalThis.Object.keys(message.inboxes).length
|
|
5138
6045
|
});
|
|
5139
|
-
const
|
|
6046
|
+
const importKeys = () => Promise.all(message.keys.map(async ({ keyId, privateKey }) => ({
|
|
5140
6047
|
keyId: new URL(keyId),
|
|
5141
6048
|
privateKey: await importJwk(privateKey, "private")
|
|
5142
6049
|
})));
|
|
5143
|
-
const
|
|
6050
|
+
const keys = worker == null ? await importKeys() : await worker.prepare("key-import", importKeys);
|
|
6051
|
+
const parseActivity = () => Activity.fromJsonLd(message.activity, {
|
|
5144
6052
|
contextLoader: this.contextLoaderFactory({
|
|
5145
6053
|
allowPrivateAddress: this.allowPrivateAddress,
|
|
5146
6054
|
userAgent: this.userAgent
|
|
@@ -5151,19 +6059,30 @@ var FederationImpl = class extends FederationBuilderImpl {
|
|
|
5151
6059
|
}),
|
|
5152
6060
|
tracerProvider: this.tracerProvider
|
|
5153
6061
|
});
|
|
5154
|
-
const
|
|
5155
|
-
|
|
5156
|
-
|
|
5157
|
-
|
|
6062
|
+
const activity = worker == null ? await parseActivity() : await worker.prepare("activity-parsing", parseActivity);
|
|
6063
|
+
const createContext = () => this.#createContext(new URL(message.baseUrl), data, {
|
|
6064
|
+
[outboundProvenance]: provenance,
|
|
6065
|
+
documentLoader: this.documentLoaderFactory({
|
|
6066
|
+
allowPrivateAddress: this.allowPrivateAddress,
|
|
6067
|
+
userAgent: this.userAgent
|
|
6068
|
+
})
|
|
6069
|
+
});
|
|
6070
|
+
const context = worker == null ? createContext() : await worker.prepare("context-preparation", createContext);
|
|
6071
|
+
worker?.useContext(context);
|
|
6072
|
+
const observation = await OutboundBroadcastObservation.resumeFanout(context, observers, message, span, parentSpanContext);
|
|
5158
6073
|
await this.sendActivity(keys, message.inboxes, activity, {
|
|
5159
6074
|
collectionSync: message.collectionSync,
|
|
5160
6075
|
orderingKey: message.orderingKey,
|
|
5161
6076
|
normalizeExistingProofs: message.normalizeExistingProofs,
|
|
5162
6077
|
activityJsonLd: message.activity,
|
|
6078
|
+
observation,
|
|
6079
|
+
worker,
|
|
5163
6080
|
context
|
|
5164
6081
|
});
|
|
5165
6082
|
}
|
|
5166
|
-
async #listenOutboxMessage(
|
|
6083
|
+
async #listenOutboxMessage(contextData, message, span, provenance, observers, worker) {
|
|
6084
|
+
const { onAttempt, onHttpExchange } = observers;
|
|
6085
|
+
worker?.enableActions();
|
|
5167
6086
|
const logger = getLogger([
|
|
5168
6087
|
"fedify",
|
|
5169
6088
|
"federation",
|
|
@@ -5180,14 +6099,26 @@ var FederationImpl = class extends FederationBuilderImpl {
|
|
|
5180
6099
|
const keys = [];
|
|
5181
6100
|
let rsaKeyPair = null;
|
|
5182
6101
|
for (const { keyId, privateKey } of message.keys) {
|
|
5183
|
-
const
|
|
6102
|
+
const importPair = async () => ({
|
|
5184
6103
|
keyId: new URL(keyId),
|
|
5185
6104
|
privateKey: await importJwk(privateKey, "private")
|
|
5186
|
-
};
|
|
6105
|
+
});
|
|
6106
|
+
const pair = worker == null ? await importPair() : await worker.prepare("key-import", importPair);
|
|
5187
6107
|
if (rsaKeyPair == null && pair.privateKey.algorithm.name === "RSASSA-PKCS1-v1_5") rsaKeyPair = pair;
|
|
5188
6108
|
keys.push(pair);
|
|
5189
6109
|
}
|
|
5190
6110
|
const loaderOptions = this.#getLoaderOptions(message.baseUrl);
|
|
6111
|
+
let observationContext;
|
|
6112
|
+
let observationDocumentLoader;
|
|
6113
|
+
let observationContextLoader;
|
|
6114
|
+
const observation = OutboundAttemptObserver.resumeOutbox(() => worker?.getContext() ?? (observationContext ??= new ContextImpl({
|
|
6115
|
+
[outboundProvenance]: provenance,
|
|
6116
|
+
federation: this,
|
|
6117
|
+
url: new URL("/", message.baseUrl),
|
|
6118
|
+
data: contextData,
|
|
6119
|
+
documentLoader: (...args) => (observationDocumentLoader ??= this.documentLoaderFactory(loaderOptions))(...args),
|
|
6120
|
+
contextLoader: (...args) => (observationContextLoader ??= this.contextLoaderFactory(loaderOptions))(...args)
|
|
6121
|
+
})), onAttempt, message, provenance, onHttpExchange, observers, observers.responseBodyCapture);
|
|
5191
6122
|
let parsedActorIds;
|
|
5192
6123
|
const getActorIds = () => {
|
|
5193
6124
|
parsedActorIds ??= (message.actorIds ?? []).flatMap((id) => {
|
|
@@ -5210,7 +6141,7 @@ var FederationImpl = class extends FederationBuilderImpl {
|
|
|
5210
6141
|
verifyPortableObject: createPortableObjectVerifier(documentLoader, contextLoader, this)
|
|
5211
6142
|
});
|
|
5212
6143
|
};
|
|
5213
|
-
const enqueueHeldOutboxMessage = async (delay, heldSince) => {
|
|
6144
|
+
const enqueueHeldOutboxMessage = async (delay, heldSince, preparationFailures) => {
|
|
5214
6145
|
const { outboxQueue } = this;
|
|
5215
6146
|
if (outboxQueue == null) return;
|
|
5216
6147
|
const heldMessage = {
|
|
@@ -5218,10 +6149,11 @@ var FederationImpl = class extends FederationBuilderImpl {
|
|
|
5218
6149
|
circuitHeld: true,
|
|
5219
6150
|
circuitHeldSince: heldSince.toString()
|
|
5220
6151
|
};
|
|
5221
|
-
|
|
6152
|
+
const enqueueOptions = {
|
|
5222
6153
|
delay: clampNegativeDelay(delay),
|
|
5223
6154
|
orderingKey: message.orderingKey
|
|
5224
|
-
}
|
|
6155
|
+
};
|
|
6156
|
+
await (worker == null ? outboxQueue.enqueue(heldMessage, enqueueOptions) : worker.enqueue("defer", heldMessage, enqueueOptions.delay, () => outboxQueue.enqueue(heldMessage, enqueueOptions), preparationFailures));
|
|
5225
6157
|
this.metrics.recordQueueTaskEnqueued({
|
|
5226
6158
|
role: "outbox",
|
|
5227
6159
|
queue: outboxQueue,
|
|
@@ -5238,7 +6170,10 @@ var FederationImpl = class extends FederationBuilderImpl {
|
|
|
5238
6170
|
heldSince
|
|
5239
6171
|
});
|
|
5240
6172
|
if (this.outboxPermanentFailureHandler != null) {
|
|
5241
|
-
const ctx = this.#createContext(new URL(message.baseUrl),
|
|
6173
|
+
const ctx = this.#createContext(new URL(message.baseUrl), contextData, {
|
|
6174
|
+
[outboundProvenance]: provenance,
|
|
6175
|
+
documentLoader: this.documentLoaderFactory(loaderOptions)
|
|
6176
|
+
});
|
|
5242
6177
|
try {
|
|
5243
6178
|
await this.outboxPermanentFailureHandler(ctx, {
|
|
5244
6179
|
reason: "circuit-breaker-ttl",
|
|
@@ -5250,6 +6185,7 @@ var FederationImpl = class extends FederationBuilderImpl {
|
|
|
5250
6185
|
actorIds: getActorIds()
|
|
5251
6186
|
});
|
|
5252
6187
|
} catch (handlerError) {
|
|
6188
|
+
worker?.dropFailure([inbox.href], handlerError);
|
|
5253
6189
|
logger.error("An unexpected error occurred in outboxPermanentFailureHandler:\n{error}", {
|
|
5254
6190
|
...logData,
|
|
5255
6191
|
error: handlerError
|
|
@@ -5260,10 +6196,13 @@ var FederationImpl = class extends FederationBuilderImpl {
|
|
|
5260
6196
|
};
|
|
5261
6197
|
const { gatewayInboxes } = message;
|
|
5262
6198
|
if (gatewayInboxes != null && gatewayInboxes.length > 1) {
|
|
5263
|
-
await this.#listenGatewayOutboxMessage(
|
|
6199
|
+
await this.#listenGatewayOutboxMessage(contextData, {
|
|
5264
6200
|
...message,
|
|
5265
6201
|
gatewayInboxes
|
|
5266
6202
|
}, span, {
|
|
6203
|
+
observation,
|
|
6204
|
+
worker,
|
|
6205
|
+
provenance,
|
|
5267
6206
|
keys,
|
|
5268
6207
|
logData,
|
|
5269
6208
|
getActorIds,
|
|
@@ -5273,7 +6212,8 @@ var FederationImpl = class extends FederationBuilderImpl {
|
|
|
5273
6212
|
return;
|
|
5274
6213
|
}
|
|
5275
6214
|
try {
|
|
5276
|
-
const
|
|
6215
|
+
const parseInbox = () => new URL(message.inbox);
|
|
6216
|
+
const inbox = worker == null ? parseInbox() : await worker.prepare("validation", parseInbox);
|
|
5277
6217
|
const circuit = this.outboxQueue == null ? void 0 : this.circuitBreaker;
|
|
5278
6218
|
const remoteHost = getRemoteHost(inbox);
|
|
5279
6219
|
let decision;
|
|
@@ -5297,13 +6237,19 @@ var FederationImpl = class extends FederationBuilderImpl {
|
|
|
5297
6237
|
return;
|
|
5298
6238
|
}
|
|
5299
6239
|
if (decision.type === "drop") {
|
|
5300
|
-
|
|
5301
|
-
|
|
6240
|
+
worker?.abandon("held-ttl-expired", [inbox.href]);
|
|
6241
|
+
const drop = async () => {
|
|
6242
|
+
const activity = await parseActivity();
|
|
6243
|
+
await dropHeldOutboxMessage(circuit, remoteHost, inbox, decision.heldSince, activity);
|
|
6244
|
+
};
|
|
6245
|
+
await (worker == null ? drop() : worker.drop([inbox.href], drop));
|
|
5302
6246
|
return;
|
|
5303
6247
|
}
|
|
5304
6248
|
if (decision.stateChange != null) recordCircuitBreakerSpanEvent(span, remoteHost, decision.stateChange);
|
|
5305
6249
|
}
|
|
5306
6250
|
await sendActivity({
|
|
6251
|
+
attemptStarted: worker?.attemptStarted,
|
|
6252
|
+
observation,
|
|
5307
6253
|
allowPrivateAddress: this.allowPrivateAddress,
|
|
5308
6254
|
keys,
|
|
5309
6255
|
activity: message.activity,
|
|
@@ -5311,7 +6257,7 @@ var FederationImpl = class extends FederationBuilderImpl {
|
|
|
5311
6257
|
activityType: message.activityType,
|
|
5312
6258
|
inbox,
|
|
5313
6259
|
sharedInbox: message.sharedInbox,
|
|
5314
|
-
headers: new Headers(message.headers),
|
|
6260
|
+
headers: worker == null ? new Headers(message.headers) : await worker.prepare("request-preparation", () => new Headers(message.headers)),
|
|
5315
6261
|
specDeterminer: new KvSpecDeterminer(this.kv, this.kvPrefixes.httpMessageSignaturesSpec, this.firstKnock, { specTtl: this.httpMessageSignaturesSpecTtl }),
|
|
5316
6262
|
meterProvider: this.meterProvider,
|
|
5317
6263
|
tracerProvider: this.tracerProvider
|
|
@@ -5331,6 +6277,7 @@ var FederationImpl = class extends FederationBuilderImpl {
|
|
|
5331
6277
|
});
|
|
5332
6278
|
}
|
|
5333
6279
|
} catch (error) {
|
|
6280
|
+
const preparationFailures = worker?.recordPreparationFailure(error, message.inbox) ?? [];
|
|
5334
6281
|
span.setStatus({
|
|
5335
6282
|
code: SpanStatusCode.ERROR,
|
|
5336
6283
|
message: String(error)
|
|
@@ -5339,7 +6286,7 @@ var FederationImpl = class extends FederationBuilderImpl {
|
|
|
5339
6286
|
if (error instanceof SendActivityError) return getRemoteHost(error.inbox);
|
|
5340
6287
|
try {
|
|
5341
6288
|
return getRemoteHost(new URL(message.inbox));
|
|
5342
|
-
} catch
|
|
6289
|
+
} catch {
|
|
5343
6290
|
logger.warn("Invalid inbox URL in queued outbox message: {inbox}", logData);
|
|
5344
6291
|
return;
|
|
5345
6292
|
}
|
|
@@ -5412,32 +6359,42 @@ var FederationImpl = class extends FederationBuilderImpl {
|
|
|
5412
6359
|
});
|
|
5413
6360
|
}
|
|
5414
6361
|
if (circuitDrop != null) {
|
|
5415
|
-
|
|
6362
|
+
worker?.abandon("held-ttl-expired", [circuitDrop.inbox.href], preparationFailures);
|
|
6363
|
+
const drop = () => dropHeldOutboxMessage(circuitDrop.circuit, circuitDrop.remoteHost, circuitDrop.inbox, circuitDrop.heldSince, activity);
|
|
6364
|
+
await (worker == null ? drop() : worker.drop([circuitDrop.inbox.href], drop));
|
|
5416
6365
|
return;
|
|
5417
6366
|
}
|
|
5418
6367
|
if (isPermanentFailure) {
|
|
6368
|
+
worker?.abandon("permanent-failure", [message.inbox], preparationFailures);
|
|
5419
6369
|
this.metrics.recordPermanentFailure(error.inbox, error.statusCode);
|
|
5420
6370
|
logger.warn("Permanent delivery failure for activity {activityId} to {inbox} ({status}); not retrying.", {
|
|
5421
6371
|
...logData,
|
|
5422
6372
|
status: error.statusCode
|
|
5423
6373
|
});
|
|
5424
6374
|
if (this.outboxPermanentFailureHandler != null) {
|
|
5425
|
-
const
|
|
5426
|
-
|
|
5427
|
-
|
|
5428
|
-
|
|
5429
|
-
inbox: new URL(message.inbox),
|
|
5430
|
-
activity,
|
|
5431
|
-
error,
|
|
5432
|
-
statusCode: error.statusCode,
|
|
5433
|
-
actorIds: getActorIds()
|
|
5434
|
-
});
|
|
5435
|
-
} catch (handlerError) {
|
|
5436
|
-
logger.error("An unexpected error occurred in outboxPermanentFailureHandler:\n{error}", {
|
|
5437
|
-
...logData,
|
|
5438
|
-
error: handlerError
|
|
6375
|
+
const drop = async () => {
|
|
6376
|
+
const ctx = this.#createContext(new URL(message.baseUrl), contextData, {
|
|
6377
|
+
[outboundProvenance]: provenance,
|
|
6378
|
+
documentLoader: this.documentLoaderFactory(loaderOptions)
|
|
5439
6379
|
});
|
|
5440
|
-
|
|
6380
|
+
try {
|
|
6381
|
+
await this.outboxPermanentFailureHandler(ctx, {
|
|
6382
|
+
reason: "http",
|
|
6383
|
+
inbox: new URL(message.inbox),
|
|
6384
|
+
activity,
|
|
6385
|
+
error,
|
|
6386
|
+
statusCode: error.statusCode,
|
|
6387
|
+
actorIds: getActorIds()
|
|
6388
|
+
});
|
|
6389
|
+
} catch (handlerError) {
|
|
6390
|
+
worker?.dropFailure([message.inbox], handlerError);
|
|
6391
|
+
logger.error("An unexpected error occurred in outboxPermanentFailureHandler:\n{error}", {
|
|
6392
|
+
...logData,
|
|
6393
|
+
error: handlerError
|
|
6394
|
+
});
|
|
6395
|
+
}
|
|
6396
|
+
};
|
|
6397
|
+
await (worker == null ? drop() : worker.drop([message.inbox], drop));
|
|
5441
6398
|
}
|
|
5442
6399
|
recordOutboxActivity(this.meterProvider, "abandoned", message.activityType);
|
|
5443
6400
|
return;
|
|
@@ -5449,7 +6406,7 @@ var FederationImpl = class extends FederationBuilderImpl {
|
|
|
5449
6406
|
});
|
|
5450
6407
|
recordCircuitBreakerHeldSpanEvent(span, circuitHold.remoteHost, circuitHold.state);
|
|
5451
6408
|
const circuit = this.circuitBreaker;
|
|
5452
|
-
await enqueueHeldOutboxMessage(retryAfterDelay == null || circuit == null ? circuitHold.delay : circuit.capHeldDelay(circuitHold.heldSince, maxDelay(circuitHold.delay, retryAfterDelay)), circuitHold.heldSince);
|
|
6409
|
+
await enqueueHeldOutboxMessage(retryAfterDelay == null || circuit == null ? circuitHold.delay : circuit.capHeldDelay(circuitHold.heldSince, maxDelay(circuitHold.delay, retryAfterDelay)), circuitHold.heldSince, preparationFailures);
|
|
5453
6410
|
return;
|
|
5454
6411
|
}
|
|
5455
6412
|
if (this.outboxQueue?.nativeRetrial && retryAfterDelay == null) {
|
|
@@ -5472,18 +6429,20 @@ var FederationImpl = class extends FederationBuilderImpl {
|
|
|
5472
6429
|
};
|
|
5473
6430
|
const { outboxQueue } = this;
|
|
5474
6431
|
if (outboxQueue != null) {
|
|
5475
|
-
|
|
6432
|
+
const enqueueOptions = {
|
|
5476
6433
|
delay: clampNegativeDelay(delay),
|
|
5477
6434
|
orderingKey: message.orderingKey
|
|
5478
|
-
}
|
|
6435
|
+
};
|
|
6436
|
+
await (worker == null ? outboxQueue.enqueue(retryMessage, enqueueOptions) : worker.enqueue("retry", retryMessage, enqueueOptions.delay, () => outboxQueue.enqueue(retryMessage, enqueueOptions), preparationFailures));
|
|
5479
6437
|
this.metrics.recordQueueTaskEnqueued({
|
|
5480
6438
|
role: "outbox",
|
|
5481
6439
|
queue: outboxQueue,
|
|
5482
6440
|
activityType: retryMessage.activityType
|
|
5483
6441
|
}, retryMessage.attempt);
|
|
5484
6442
|
recordOutboxActivity(this.meterProvider, "retried", retryMessage.activityType);
|
|
5485
|
-
}
|
|
6443
|
+
} else worker?.abandon("queue-unavailable", [message.inbox], preparationFailures);
|
|
5486
6444
|
} else {
|
|
6445
|
+
worker?.abandon("retry-policy-declined", [message.inbox], preparationFailures);
|
|
5487
6446
|
logger.error("Failed to send activity {activityId} to {inbox} after {attempt} attempts; giving up:\n{error}", {
|
|
5488
6447
|
...logData,
|
|
5489
6448
|
error
|
|
@@ -5518,25 +6477,32 @@ var FederationImpl = class extends FederationBuilderImpl {
|
|
|
5518
6477
|
"federation",
|
|
5519
6478
|
"circuit"
|
|
5520
6479
|
]);
|
|
5521
|
-
const { keys, logData, getActorIds, parseActivity, dropHeldOutboxMessage } = helpers;
|
|
6480
|
+
const { observation, worker, provenance, keys, logData, getActorIds, parseActivity, dropHeldOutboxMessage } = helpers;
|
|
5522
6481
|
const circuit = this.outboxQueue == null ? void 0 : this.circuitBreaker;
|
|
5523
6482
|
const specDeterminer = new KvSpecDeterminer(this.kv, this.kvPrefixes.httpMessageSignaturesSpec, this.firstKnock, { specTtl: this.httpMessageSignaturesSpecTtl });
|
|
5524
6483
|
const survivors = [];
|
|
6484
|
+
const preparationFailuresByRoute = /* @__PURE__ */ new Map();
|
|
6485
|
+
const permanentRoutes = /* @__PURE__ */ new Set();
|
|
5525
6486
|
let lastError;
|
|
5526
6487
|
let lastPermanent;
|
|
5527
6488
|
let activity;
|
|
5528
6489
|
const getActivity = async () => activity ??= await parseActivity();
|
|
5529
6490
|
const drop = async (circuit, remoteHost, inbox, heldSince) => {
|
|
5530
|
-
|
|
6491
|
+
const routes = message.gatewayInboxes.filter((route) => !permanentRoutes.has(route));
|
|
6492
|
+
worker?.abandon("held-ttl-expired", routes, routes.flatMap((route) => preparationFailuresByRoute.get(route) ?? []));
|
|
6493
|
+
const execute = async () => dropHeldOutboxMessage(circuit, remoteHost, inbox, heldSince, await getActivity());
|
|
6494
|
+
await (worker == null ? execute() : worker.drop(routes, execute));
|
|
5531
6495
|
};
|
|
5532
6496
|
const heldSince = parseInstant(message.circuitHeldSince);
|
|
5533
6497
|
if (circuit != null && heldSince != null && Temporal.Instant.compare(heldSince.add(circuit.options.heldActivityTtl), Temporal.Now.instant()) <= 0) {
|
|
5534
|
-
const
|
|
6498
|
+
const parseInbox = () => new URL(message.gatewayInboxes[0]);
|
|
6499
|
+
const inbox = worker == null ? parseInbox() : await worker.prepare("validation", parseInbox);
|
|
5535
6500
|
await drop(circuit, getRemoteHost(inbox), inbox, heldSince);
|
|
5536
6501
|
return;
|
|
5537
6502
|
}
|
|
5538
6503
|
for (const inboxHref of message.gatewayInboxes) {
|
|
5539
|
-
const
|
|
6504
|
+
const parseInbox = () => new URL(inboxHref);
|
|
6505
|
+
const inbox = worker == null ? parseInbox() : await worker.prepare("validation", parseInbox);
|
|
5540
6506
|
const remoteHost = getRemoteHost(inbox);
|
|
5541
6507
|
const now = Temporal.Now.instant();
|
|
5542
6508
|
const notBefore = parseInstant(message.gatewayNotBefore?.[inboxHref]);
|
|
@@ -5574,6 +6540,8 @@ var FederationImpl = class extends FederationBuilderImpl {
|
|
|
5574
6540
|
}
|
|
5575
6541
|
try {
|
|
5576
6542
|
await sendActivity({
|
|
6543
|
+
attemptStarted: worker?.attemptStarted,
|
|
6544
|
+
observation,
|
|
5577
6545
|
allowPrivateAddress: this.allowPrivateAddress,
|
|
5578
6546
|
keys,
|
|
5579
6547
|
activity: message.activity,
|
|
@@ -5581,12 +6549,14 @@ var FederationImpl = class extends FederationBuilderImpl {
|
|
|
5581
6549
|
activityType: message.activityType,
|
|
5582
6550
|
inbox,
|
|
5583
6551
|
sharedInbox: message.sharedInbox,
|
|
5584
|
-
headers: new Headers(message.headers),
|
|
6552
|
+
headers: worker == null ? new Headers(message.headers) : await worker.prepare("request-preparation", () => new Headers(message.headers)),
|
|
5585
6553
|
specDeterminer,
|
|
5586
6554
|
meterProvider: this.meterProvider,
|
|
5587
6555
|
tracerProvider: this.tracerProvider
|
|
5588
6556
|
});
|
|
5589
6557
|
} catch (error) {
|
|
6558
|
+
const preparationFailures = worker?.recordPreparationFailure(error, inboxHref) ?? [];
|
|
6559
|
+
if (preparationFailures.length > 0) preparationFailuresByRoute.set(inboxHref, preparationFailures);
|
|
5590
6560
|
lastError = error;
|
|
5591
6561
|
const isPermanentFailure = error instanceof SendActivityError && this.permanentFailureStatusCodes.includes(error.statusCode);
|
|
5592
6562
|
let retryAfter = !isPermanentFailure && error instanceof SendActivityError && (error.statusCode === 429 || error.statusCode === 503) ? parseRetryAfter(error.responseHeaders) : void 0;
|
|
@@ -5631,6 +6601,8 @@ var FederationImpl = class extends FederationBuilderImpl {
|
|
|
5631
6601
|
});
|
|
5632
6602
|
}
|
|
5633
6603
|
if (isPermanentFailure) {
|
|
6604
|
+
permanentRoutes.add(inboxHref);
|
|
6605
|
+
worker?.abandon("permanent-failure", [inboxHref], preparationFailures);
|
|
5634
6606
|
logger.warn("Permanent delivery failure for activity {activityId} to {inbox} ({status}); not trying the gateway again.", {
|
|
5635
6607
|
...logData,
|
|
5636
6608
|
inbox: inboxHref,
|
|
@@ -5695,22 +6667,29 @@ var FederationImpl = class extends FederationBuilderImpl {
|
|
|
5695
6667
|
portableInbox: message.portableInbox
|
|
5696
6668
|
});
|
|
5697
6669
|
if (this.outboxPermanentFailureHandler != null) {
|
|
5698
|
-
const
|
|
5699
|
-
|
|
5700
|
-
|
|
5701
|
-
|
|
5702
|
-
inbox,
|
|
5703
|
-
activity: await getActivity(),
|
|
5704
|
-
error,
|
|
5705
|
-
statusCode: error.statusCode,
|
|
5706
|
-
actorIds: getActorIds()
|
|
5707
|
-
});
|
|
5708
|
-
} catch (handlerError) {
|
|
5709
|
-
logger.error("An unexpected error occurred in outboxPermanentFailureHandler:\n{error}", {
|
|
5710
|
-
...logData,
|
|
5711
|
-
error: handlerError
|
|
6670
|
+
const drop = async () => {
|
|
6671
|
+
const ctx = this.#createContext(new URL(message.baseUrl), contextData, {
|
|
6672
|
+
[outboundProvenance]: provenance,
|
|
6673
|
+
documentLoader: this.documentLoaderFactory(this.#getLoaderOptions(message.baseUrl))
|
|
5712
6674
|
});
|
|
5713
|
-
|
|
6675
|
+
try {
|
|
6676
|
+
await this.outboxPermanentFailureHandler(ctx, {
|
|
6677
|
+
reason: "http",
|
|
6678
|
+
inbox,
|
|
6679
|
+
activity: await getActivity(),
|
|
6680
|
+
error,
|
|
6681
|
+
statusCode: error.statusCode,
|
|
6682
|
+
actorIds: getActorIds()
|
|
6683
|
+
});
|
|
6684
|
+
} catch (handlerError) {
|
|
6685
|
+
worker?.dropFailure(message.gatewayInboxes, handlerError);
|
|
6686
|
+
logger.error("An unexpected error occurred in outboxPermanentFailureHandler:\n{error}", {
|
|
6687
|
+
...logData,
|
|
6688
|
+
error: handlerError
|
|
6689
|
+
});
|
|
6690
|
+
}
|
|
6691
|
+
};
|
|
6692
|
+
await (worker == null ? drop() : worker.drop(message.gatewayInboxes, drop));
|
|
5714
6693
|
}
|
|
5715
6694
|
recordOutboxActivity(this.meterProvider, "abandoned", message.activityType);
|
|
5716
6695
|
return;
|
|
@@ -5746,10 +6725,11 @@ var FederationImpl = class extends FederationBuilderImpl {
|
|
|
5746
6725
|
...logData,
|
|
5747
6726
|
portableInbox: message.portableInbox
|
|
5748
6727
|
});
|
|
5749
|
-
|
|
6728
|
+
const enqueueOptions = {
|
|
5750
6729
|
delay: clampNegativeDelay(delay),
|
|
5751
6730
|
orderingKey: message.orderingKey
|
|
5752
|
-
}
|
|
6731
|
+
};
|
|
6732
|
+
await (worker == null ? outboxQueue.enqueue(heldMessage, enqueueOptions) : worker.enqueue("defer", heldMessage, enqueueOptions.delay, () => outboxQueue.enqueue(heldMessage, enqueueOptions), survivors.flatMap((route) => preparationFailuresByRoute.get(route.inbox) ?? [])));
|
|
5753
6733
|
this.metrics.recordQueueTaskEnqueued({
|
|
5754
6734
|
role: "outbox",
|
|
5755
6735
|
queue: outboxQueue,
|
|
@@ -5767,6 +6747,7 @@ var FederationImpl = class extends FederationBuilderImpl {
|
|
|
5767
6747
|
throw lastError;
|
|
5768
6748
|
}
|
|
5769
6749
|
if (policyDelay == null || outboxQueue == null) {
|
|
6750
|
+
worker?.abandon(outboxQueue == null ? "queue-unavailable" : "retry-policy-declined", survivors.map((route) => route.inbox), survivors.flatMap((route) => preparationFailuresByRoute.get(route.inbox) ?? []));
|
|
5770
6751
|
logger.error("Failed to send activity {activityId} to every gateway of {portableInbox} after {attempt} attempts; giving up:\n{error}", {
|
|
5771
6752
|
...logData,
|
|
5772
6753
|
portableInbox: message.portableInbox,
|
|
@@ -5785,10 +6766,11 @@ var FederationImpl = class extends FederationBuilderImpl {
|
|
|
5785
6766
|
portableInbox: message.portableInbox,
|
|
5786
6767
|
error: lastError
|
|
5787
6768
|
});
|
|
5788
|
-
|
|
6769
|
+
const enqueueOptions = {
|
|
5789
6770
|
delay: clampNegativeDelay(delay),
|
|
5790
6771
|
orderingKey: message.orderingKey
|
|
5791
|
-
}
|
|
6772
|
+
};
|
|
6773
|
+
await (worker == null ? outboxQueue.enqueue(retryMessage, enqueueOptions) : worker.enqueue("retry", retryMessage, enqueueOptions.delay, () => outboxQueue.enqueue(retryMessage, enqueueOptions), survivors.flatMap((route) => preparationFailuresByRoute.get(route.inbox) ?? [])));
|
|
5792
6774
|
this.metrics.recordQueueTaskEnqueued({
|
|
5793
6775
|
role: "outbox",
|
|
5794
6776
|
queue: outboxQueue,
|
|
@@ -5796,18 +6778,24 @@ var FederationImpl = class extends FederationBuilderImpl {
|
|
|
5796
6778
|
}, retryMessage.attempt);
|
|
5797
6779
|
recordOutboxActivity(this.meterProvider, "retried", retryMessage.activityType);
|
|
5798
6780
|
}
|
|
5799
|
-
async #listenInboxMessage(ctxData, message, span, onActivityType) {
|
|
6781
|
+
async #listenInboxMessage(ctxData, message, span, provenance, onActivityType) {
|
|
5800
6782
|
const logger = getLogger([
|
|
5801
6783
|
"fedify",
|
|
5802
6784
|
"federation",
|
|
5803
6785
|
"inbox"
|
|
5804
6786
|
]);
|
|
5805
6787
|
const baseUrl = new URL(message.baseUrl);
|
|
5806
|
-
let context = this.#createContext(baseUrl, ctxData);
|
|
5807
|
-
if (message.identifier != null && this.actorCallbacks?.keyPairsDispatcher != null) context = this.#createContext(baseUrl, ctxData, {
|
|
6788
|
+
let context = this.#createContext(baseUrl, ctxData, { [outboundProvenance]: provenance });
|
|
6789
|
+
if (message.identifier != null && this.actorCallbacks?.keyPairsDispatcher != null) context = this.#createContext(baseUrl, ctxData, {
|
|
6790
|
+
[outboundProvenance]: provenance,
|
|
6791
|
+
documentLoader: await context.getDocumentLoader({ identifier: message.identifier })
|
|
6792
|
+
});
|
|
5808
6793
|
else if (message.identifier == null && this.sharedInboxKeyDispatcher != null) {
|
|
5809
6794
|
const identity = await this.sharedInboxKeyDispatcher(context);
|
|
5810
|
-
if (identity != null) context = this.#createContext(baseUrl, ctxData, {
|
|
6795
|
+
if (identity != null) context = this.#createContext(baseUrl, ctxData, {
|
|
6796
|
+
[outboundProvenance]: provenance,
|
|
6797
|
+
documentLoader: "identifier" in identity || "username" in identity ? await context.getDocumentLoader(identity) : context.getDocumentLoader(identity)
|
|
6798
|
+
});
|
|
5811
6799
|
}
|
|
5812
6800
|
await this._getTracer().startActiveSpan("activitypub.dispatch_inbox_listener", { kind: SpanKind.INTERNAL }, async (listenerSpan) => {
|
|
5813
6801
|
let activity = null;
|
|
@@ -6230,6 +7218,7 @@ var FederationImpl = class extends FederationBuilderImpl {
|
|
|
6230
7218
|
}
|
|
6231
7219
|
const loaderOptions = this.#getLoaderOptions(url.origin);
|
|
6232
7220
|
const ctxOptions = {
|
|
7221
|
+
[outboundProvenance]: opts[outboundProvenance],
|
|
6233
7222
|
url,
|
|
6234
7223
|
federation: this,
|
|
6235
7224
|
data: contextData,
|
|
@@ -6261,22 +7250,37 @@ var FederationImpl = class extends FederationBuilderImpl {
|
|
|
6261
7250
|
"federation",
|
|
6262
7251
|
"outbox"
|
|
6263
7252
|
]);
|
|
6264
|
-
const { immediate, collectionSync, orderingKey, context: ctx } = options;
|
|
6265
|
-
|
|
6266
|
-
|
|
6267
|
-
|
|
6268
|
-
|
|
6269
|
-
|
|
6270
|
-
|
|
6271
|
-
|
|
6272
|
-
|
|
6273
|
-
|
|
6274
|
-
|
|
6275
|
-
|
|
6276
|
-
|
|
7253
|
+
const { immediate, collectionSync, orderingKey, context: ctx, observation, worker, provenance = getOutboundProvenance(ctx) } = options;
|
|
7254
|
+
observation?.setPhase("validation");
|
|
7255
|
+
const validateActivity = () => {
|
|
7256
|
+
if (activity.id == null) throw new TypeError("The activity to send must have an id.");
|
|
7257
|
+
if (activity.actorId == null) throw new TypeError("The activity to send must have at least one actor property.");
|
|
7258
|
+
else if (keys.length < 1) throw new TypeError("The keys must not be empty.");
|
|
7259
|
+
return activity.id.href;
|
|
7260
|
+
};
|
|
7261
|
+
const activityId = worker == null ? validateActivity() : await worker.prepare("validation", validateActivity);
|
|
7262
|
+
observation?.setPhase("serialization");
|
|
7263
|
+
const createContextLoader = () => this.contextLoaderFactory(this.#getLoaderOptions(ctx.origin));
|
|
7264
|
+
const contextLoader = worker == null ? createContextLoader() : await worker.prepare("serialization", createContextLoader);
|
|
7265
|
+
const resolveRsaKey = () => {
|
|
7266
|
+
const ldSignable = !hasPortableActor(activity);
|
|
7267
|
+
let rsaKey = null;
|
|
7268
|
+
for (const { keyId, privateKey } of keys) {
|
|
7269
|
+
validateCryptoKey(privateKey, "private");
|
|
7270
|
+
if (ldSignable && rsaKey == null && privateKey.algorithm.name === "RSASSA-PKCS1-v1_5" && !isPortableKeyId(keyId)) rsaKey = {
|
|
7271
|
+
keyId,
|
|
7272
|
+
privateKey
|
|
7273
|
+
};
|
|
7274
|
+
}
|
|
7275
|
+
return {
|
|
7276
|
+
rsaKey,
|
|
7277
|
+
ldSignable
|
|
6277
7278
|
};
|
|
6278
|
-
}
|
|
6279
|
-
|
|
7279
|
+
};
|
|
7280
|
+
observation?.setPhase("key-resolution");
|
|
7281
|
+
const { rsaKey, ldSignable } = worker == null ? resolveRsaKey() : await worker.prepare("key-resolution", resolveRsaKey);
|
|
7282
|
+
observation?.setPhase("signing");
|
|
7283
|
+
const signActivity = () => signOutgoingActivity(activity, keys.map(({ keyId, privateKey }) => ({
|
|
6280
7284
|
verificationMethod: keyId,
|
|
6281
7285
|
privateKey
|
|
6282
7286
|
})), {
|
|
@@ -6284,14 +7288,23 @@ var FederationImpl = class extends FederationBuilderImpl {
|
|
|
6284
7288
|
tracerProvider: this.tracerProvider,
|
|
6285
7289
|
appendToExistingProofs: false
|
|
6286
7290
|
});
|
|
7291
|
+
const signed = worker == null ? await signActivity() : await worker.prepare("signing", signActivity);
|
|
6287
7292
|
activity = signed.activity;
|
|
6288
7293
|
const { hasProof, proofCreated } = signed;
|
|
6289
|
-
|
|
6290
|
-
|
|
6291
|
-
|
|
6292
|
-
|
|
6293
|
-
|
|
6294
|
-
|
|
7294
|
+
observation?.setPhase("serialization");
|
|
7295
|
+
const serializeActivity = async () => {
|
|
7296
|
+
let result = !proofCreated && options.activityJsonLd != null ? options.activityJsonLd : await activity.toJsonLd({
|
|
7297
|
+
format: "compact",
|
|
7298
|
+
contextLoader
|
|
7299
|
+
});
|
|
7300
|
+
if (proofCreated || !hasProof || options.normalizeExistingProofs) result = await normalizeOutgoingActivityJsonLd(result, contextLoader, { preserveNestedSecuredDocuments: true });
|
|
7301
|
+
return result;
|
|
7302
|
+
};
|
|
7303
|
+
let jsonLd = worker == null ? await serializeActivity() : await worker.prepare("serialization", serializeActivity);
|
|
7304
|
+
observation?.setPhase("validation");
|
|
7305
|
+
const validateProofShape = () => assertSupportedCompoundProofShape(jsonLd, activityId);
|
|
7306
|
+
if (worker == null) validateProofShape();
|
|
7307
|
+
else await worker.prepare("validation", validateProofShape);
|
|
6295
7308
|
if (rsaKey == null && !ldSignable) logger.debug("The activity {activityId} is performed by a portable actor, so it is sent without a Linked Data signature.", { activityId });
|
|
6296
7309
|
else if (rsaKey == null) logger.warn("No supported key found to create a Linked Data signature for the activity {activityId}. The activity will be sent without a Linked Data signature. In order to create a Linked Data signature, at least one RSASSA-PKCS1-v1_5 key must be provided.", {
|
|
6297
7310
|
activityId,
|
|
@@ -6300,17 +7313,24 @@ var FederationImpl = class extends FederationBuilderImpl {
|
|
|
6300
7313
|
privateKey: pair.privateKey
|
|
6301
7314
|
}))
|
|
6302
7315
|
});
|
|
6303
|
-
else
|
|
6304
|
-
|
|
6305
|
-
|
|
6306
|
-
|
|
6307
|
-
|
|
6308
|
-
|
|
6309
|
-
|
|
6310
|
-
|
|
6311
|
-
|
|
6312
|
-
|
|
6313
|
-
|
|
7316
|
+
else {
|
|
7317
|
+
const signLinkedData = async () => {
|
|
7318
|
+
try {
|
|
7319
|
+
observation?.setPhase("signing");
|
|
7320
|
+
return await signJsonLd(jsonLd, rsaKey.privateKey, rsaKey.keyId, {
|
|
7321
|
+
contextLoader,
|
|
7322
|
+
tracerProvider: this.tracerProvider
|
|
7323
|
+
});
|
|
7324
|
+
} catch (error) {
|
|
7325
|
+
if (!isLinkedDataSignatureJsonLdProcessingError(error)) throw error;
|
|
7326
|
+
logger.warn("Failed to create a Linked Data signature for the activity {activityId}. The activity will be sent without a Linked Data signature.", {
|
|
7327
|
+
activityId,
|
|
7328
|
+
error
|
|
7329
|
+
});
|
|
7330
|
+
return jsonLd;
|
|
7331
|
+
}
|
|
7332
|
+
};
|
|
7333
|
+
jsonLd = worker == null ? await signLinkedData() : await worker.prepare("signing", signLinkedData);
|
|
6314
7334
|
}
|
|
6315
7335
|
if (!hasProof) logger.warn("No supported key found to create a proof for the activity {activityId}. The activity will be sent without a proof. In order to create a proof, at least one Ed25519 key must be provided.", {
|
|
6316
7336
|
activityId,
|
|
@@ -6319,6 +7339,7 @@ var FederationImpl = class extends FederationBuilderImpl {
|
|
|
6319
7339
|
privateKey: pair.privateKey
|
|
6320
7340
|
}))
|
|
6321
7341
|
});
|
|
7342
|
+
observation?.setPhase("request-preparation");
|
|
6322
7343
|
if (immediate || this.outboxQueue == null) {
|
|
6323
7344
|
if (immediate) logger.debug("Sending activity immediately without queue since immediate option is set.", {
|
|
6324
7345
|
activityId: activity.id.href,
|
|
@@ -6329,18 +7350,40 @@ var FederationImpl = class extends FederationBuilderImpl {
|
|
|
6329
7350
|
activity: jsonLd
|
|
6330
7351
|
});
|
|
6331
7352
|
const promises = [];
|
|
6332
|
-
for (const inbox in inboxes)
|
|
6333
|
-
|
|
6334
|
-
|
|
6335
|
-
|
|
6336
|
-
|
|
6337
|
-
|
|
6338
|
-
|
|
6339
|
-
|
|
6340
|
-
|
|
6341
|
-
|
|
6342
|
-
|
|
6343
|
-
|
|
7353
|
+
for (const inbox in inboxes) {
|
|
7354
|
+
const delivery = observation?.delivery(inbox);
|
|
7355
|
+
let sending;
|
|
7356
|
+
try {
|
|
7357
|
+
const createHeaders = collectionSync == null ? void 0 : async () => new Headers({ "Collection-Synchronization": await buildCollectionSynchronizationHeader(collectionSync, inboxes[inbox].actorIds) });
|
|
7358
|
+
const sendActivityId = activity.id?.href;
|
|
7359
|
+
const activityType = getTypeId(activity).href;
|
|
7360
|
+
observation?.setActivity({
|
|
7361
|
+
...sendActivityId == null ? {} : { id: sendActivityId },
|
|
7362
|
+
type: activityType
|
|
7363
|
+
});
|
|
7364
|
+
sending = sendActivityToInbox(inbox, inboxes[inbox].gatewayInboxes, {
|
|
7365
|
+
attemptStarted: worker == null ? void 0 : (target) => worker.directAttemptStarted(target, observation?.continuation(inbox)?.outbound),
|
|
7366
|
+
observation: delivery,
|
|
7367
|
+
allowPrivateAddress: this.allowPrivateAddress,
|
|
7368
|
+
keys,
|
|
7369
|
+
activity: jsonLd,
|
|
7370
|
+
activityId: sendActivityId,
|
|
7371
|
+
activityType,
|
|
7372
|
+
sharedInbox: inboxes[inbox].sharedInbox,
|
|
7373
|
+
headers: createHeaders == null ? void 0 : await (worker == null ? createHeaders() : worker.prepare("request-preparation", createHeaders)),
|
|
7374
|
+
specDeterminer: new KvSpecDeterminer(this.kv, this.kvPrefixes.httpMessageSignaturesSpec, this.firstKnock, { specTtl: this.httpMessageSignaturesSpecTtl }),
|
|
7375
|
+
meterProvider: this.meterProvider,
|
|
7376
|
+
tracerProvider: this.tracerProvider
|
|
7377
|
+
});
|
|
7378
|
+
} catch (error) {
|
|
7379
|
+
if (delivery != null) await delivery.failed(error);
|
|
7380
|
+
throw error;
|
|
7381
|
+
}
|
|
7382
|
+
if (observation != null) sending.catch(() => {});
|
|
7383
|
+
if (delivery != null) await delivery.handoff({ type: "direct" });
|
|
7384
|
+
promises.push(sending);
|
|
7385
|
+
}
|
|
7386
|
+
if (observation != null) await observation.prepared("delivery");
|
|
6344
7387
|
await Promise.all(promises);
|
|
6345
7388
|
return;
|
|
6346
7389
|
}
|
|
@@ -6348,48 +7391,71 @@ var FederationImpl = class extends FederationBuilderImpl {
|
|
|
6348
7391
|
activityId: activity.id.href,
|
|
6349
7392
|
activity: jsonLd
|
|
6350
7393
|
});
|
|
7394
|
+
observation?.setPhase("key-resolution");
|
|
6351
7395
|
const keyJwkPairs = [];
|
|
6352
7396
|
for (const { keyId, privateKey } of keys) {
|
|
6353
|
-
const
|
|
7397
|
+
const exportPrivateKey = () => exportJwk(privateKey);
|
|
7398
|
+
const privateKeyJwk = worker == null ? await exportPrivateKey() : await worker.prepare("key-export", exportPrivateKey);
|
|
6354
7399
|
keyJwkPairs.push({
|
|
6355
7400
|
keyId: keyId.href,
|
|
6356
7401
|
privateKey: privateKeyJwk
|
|
6357
7402
|
});
|
|
6358
7403
|
}
|
|
6359
7404
|
if (!this.manuallyStartQueue) this._startQueueInternal(ctx.data);
|
|
7405
|
+
observation?.setPhase("request-preparation");
|
|
6360
7406
|
const carrier = {};
|
|
6361
|
-
propagation.inject(context.active(), carrier);
|
|
7407
|
+
const injectTraceContext = () => propagation.inject(context.active(), carrier);
|
|
7408
|
+
if (worker == null) injectTraceContext();
|
|
7409
|
+
else await worker.prepare("request-preparation", injectTraceContext);
|
|
6362
7410
|
const messages = [];
|
|
6363
|
-
for (const inbox in inboxes) {
|
|
6364
|
-
const
|
|
6365
|
-
|
|
6366
|
-
|
|
6367
|
-
|
|
6368
|
-
|
|
6369
|
-
|
|
6370
|
-
|
|
6371
|
-
|
|
6372
|
-
|
|
6373
|
-
|
|
6374
|
-
|
|
6375
|
-
|
|
6376
|
-
|
|
6377
|
-
|
|
6378
|
-
|
|
6379
|
-
|
|
6380
|
-
|
|
6381
|
-
|
|
6382
|
-
|
|
7411
|
+
for (const inbox in inboxes) try {
|
|
7412
|
+
const prepareMessage = async () => {
|
|
7413
|
+
const { portableInbox, gatewayInboxes } = inboxes[inbox];
|
|
7414
|
+
const messageOrderingKey = orderingKey == null ? void 0 : `${orderingKey}\n${getOrderingDestination(inbox, portableInbox)}`;
|
|
7415
|
+
const messageActivityId = activity.id?.href;
|
|
7416
|
+
const messageActivityType = getTypeId(activity).href;
|
|
7417
|
+
observation?.setActivity({
|
|
7418
|
+
...messageActivityId == null ? {} : { id: messageActivityId },
|
|
7419
|
+
type: messageActivityType
|
|
7420
|
+
});
|
|
7421
|
+
return {
|
|
7422
|
+
message: {
|
|
7423
|
+
type: "outbox",
|
|
7424
|
+
...serializeProvenance(provenance),
|
|
7425
|
+
id: crypto.randomUUID(),
|
|
7426
|
+
baseUrl: ctx.origin,
|
|
7427
|
+
keys: keyJwkPairs,
|
|
7428
|
+
activity: jsonLd,
|
|
7429
|
+
activityId: messageActivityId,
|
|
7430
|
+
activityType: messageActivityType,
|
|
7431
|
+
inbox,
|
|
7432
|
+
...observation?.continuation(inbox),
|
|
7433
|
+
sharedInbox: inboxes[inbox].sharedInbox,
|
|
7434
|
+
actorIds: [...inboxes[inbox].actorIds],
|
|
7435
|
+
started: (/* @__PURE__ */ new Date()).toISOString(),
|
|
7436
|
+
attempt: 0,
|
|
7437
|
+
headers: collectionSync == null ? {} : { "Collection-Synchronization": await buildCollectionSynchronizationHeader(collectionSync, inboxes[inbox].actorIds) },
|
|
7438
|
+
orderingKey: messageOrderingKey,
|
|
7439
|
+
...getPortableMessageFields(portableInbox, gatewayInboxes),
|
|
7440
|
+
traceContext: carrier
|
|
7441
|
+
},
|
|
7442
|
+
orderingKey: messageOrderingKey
|
|
7443
|
+
};
|
|
6383
7444
|
};
|
|
6384
|
-
messages.push(
|
|
6385
|
-
|
|
6386
|
-
|
|
6387
|
-
|
|
7445
|
+
messages.push(await (worker == null ? prepareMessage() : worker.prepare("request-preparation", prepareMessage)));
|
|
7446
|
+
} catch (error) {
|
|
7447
|
+
if (observation != null) await observation.delivery(inbox)?.failed(error);
|
|
7448
|
+
throw error;
|
|
6388
7449
|
}
|
|
7450
|
+
if (observation != null) await observation.prepared("delivery");
|
|
6389
7451
|
const { outboxQueue } = this;
|
|
6390
7452
|
if (outboxQueue.enqueueMany == null || orderingKey != null) {
|
|
6391
7453
|
const errors = (await Promise.allSettled(messages.map(async (m) => {
|
|
6392
|
-
|
|
7454
|
+
const enqueue = () => worker == null ? outboxQueue.enqueue(m.message, { orderingKey: m.orderingKey }) : worker.enqueueChildren([m.message], () => outboxQueue.enqueue(m.message, { orderingKey: m.orderingKey }));
|
|
7455
|
+
await (observation == null ? enqueue() : observation.enqueue([{
|
|
7456
|
+
inbox: m.message.inbox,
|
|
7457
|
+
queueMessageId: m.message.id
|
|
7458
|
+
}], "outbox", enqueue));
|
|
6393
7459
|
recordOutboxEnqueue(this.meterProvider, outboxQueue, m.message);
|
|
6394
7460
|
}))).filter((r) => r.status === "rejected").map((r) => r.reason);
|
|
6395
7461
|
if (errors.length > 0) {
|
|
@@ -6402,7 +7468,11 @@ var FederationImpl = class extends FederationBuilderImpl {
|
|
|
6402
7468
|
}
|
|
6403
7469
|
} else {
|
|
6404
7470
|
try {
|
|
6405
|
-
|
|
7471
|
+
const enqueue = () => worker == null ? outboxQueue.enqueueMany(messages.map((m) => m.message)) : worker.enqueueChildren(messages.map((m) => m.message), () => outboxQueue.enqueueMany(messages.map((m) => m.message)));
|
|
7472
|
+
await (observation == null ? enqueue() : observation.enqueue(messages.map(({ message }) => ({
|
|
7473
|
+
inbox: message.inbox,
|
|
7474
|
+
queueMessageId: message.id
|
|
7475
|
+
})), "outbox", enqueue));
|
|
6406
7476
|
} catch (error) {
|
|
6407
7477
|
logger.error("Failed to enqueue activity {activityId} to send later: {error}", {
|
|
6408
7478
|
activityId: activity.id.href,
|
|
@@ -6414,9 +7484,16 @@ var FederationImpl = class extends FederationBuilderImpl {
|
|
|
6414
7484
|
}
|
|
6415
7485
|
}
|
|
6416
7486
|
fetch(request, options) {
|
|
6417
|
-
|
|
7487
|
+
const requestId = getRequestId(request);
|
|
7488
|
+
return withContext({
|
|
7489
|
+
requestId,
|
|
7490
|
+
inboxRequestId: void 0,
|
|
7491
|
+
dispatchId: void 0,
|
|
7492
|
+
sourceInboxRequestId: void 0,
|
|
7493
|
+
sourceRequestId: void 0
|
|
7494
|
+
}, async () => {
|
|
6418
7495
|
const tracer = this._getTracer();
|
|
6419
|
-
const metricState = {};
|
|
7496
|
+
const metricState = { requestId };
|
|
6420
7497
|
const metricStart = performance.now();
|
|
6421
7498
|
return await tracer.startActiveSpan(request.method, {
|
|
6422
7499
|
kind: SpanKind.SERVER,
|
|
@@ -6461,7 +7538,8 @@ var FederationImpl = class extends FederationBuilderImpl {
|
|
|
6461
7538
|
logger.error("An error occurred while serving request {method} {url}: {error}", {
|
|
6462
7539
|
method: request.method,
|
|
6463
7540
|
url: request.url,
|
|
6464
|
-
error
|
|
7541
|
+
error,
|
|
7542
|
+
...metricState.inboxRequest == null ? {} : { inboxRequestId: metricState.inboxRequest.inboxRequestId }
|
|
6465
7543
|
});
|
|
6466
7544
|
throw error;
|
|
6467
7545
|
}
|
|
@@ -6481,6 +7559,7 @@ var FederationImpl = class extends FederationBuilderImpl {
|
|
|
6481
7559
|
const url = new URL(request.url);
|
|
6482
7560
|
const logTpl = "{method} {path}: {status}";
|
|
6483
7561
|
const values = {
|
|
7562
|
+
...metricState.inboxRequest == null ? {} : { inboxRequestId: metricState.inboxRequest.inboxRequestId },
|
|
6484
7563
|
method: request.method,
|
|
6485
7564
|
path: `${url.pathname}${url.search}`,
|
|
6486
7565
|
url: request.url,
|
|
@@ -6533,9 +7612,11 @@ var FederationImpl = class extends FederationBuilderImpl {
|
|
|
6533
7612
|
metricState.endpoint = getEndpointCategory(route.name);
|
|
6534
7613
|
span.updateName(`${request.method} ${route.template}`);
|
|
6535
7614
|
const routeName = route.name.replace(/:.*$/, "");
|
|
7615
|
+
const source = request.method === "POST" && (routeName === "inbox" || routeName === "sharedInbox") ? this.#createInboxRequestSource(request, metricState) : void 0;
|
|
7616
|
+
const provenance = globalThis.Object.freeze(source == null ? { originatingRequestId: metricState.requestId } : { inboxRequest: source });
|
|
6536
7617
|
let context;
|
|
6537
7618
|
try {
|
|
6538
|
-
context = this.#createContext(request, contextData);
|
|
7619
|
+
context = withContext(source == null ? {} : { inboxRequestId: source.inboxRequestId }, () => this.#createContext(request, contextData, { [outboundProvenance]: provenance }));
|
|
6539
7620
|
} catch (error) {
|
|
6540
7621
|
if (request.method !== "POST" || routeName !== "inbox" && routeName !== "sharedInbox") throw error;
|
|
6541
7622
|
return await this.#reportInboxPreparationError(request, contextData, {
|
|
@@ -6606,13 +7687,19 @@ var FederationImpl = class extends FederationBuilderImpl {
|
|
|
6606
7687
|
const observation = new InboxObservation({
|
|
6607
7688
|
kind: routeName === "inbox" ? "personal" : "shared",
|
|
6608
7689
|
recipient
|
|
6609
|
-
});
|
|
7690
|
+
}, source);
|
|
6610
7691
|
this.#setInboxCompletion(metricState, observation, () => context);
|
|
6611
|
-
return await observation.run(async () => {
|
|
6612
|
-
if (routeName === "inbox") context = this.#createContext(request, contextData, {
|
|
7692
|
+
return await withContext({ inboxRequestId: source.inboxRequestId }, () => observation.run(async () => {
|
|
7693
|
+
if (routeName === "inbox") context = this.#createContext(request, contextData, {
|
|
7694
|
+
[outboundProvenance]: getOutboundProvenance(context),
|
|
7695
|
+
documentLoader: await context.getDocumentLoader({ identifier: recipient })
|
|
7696
|
+
});
|
|
6613
7697
|
else if (this.sharedInboxKeyDispatcher != null) {
|
|
6614
7698
|
const identity = await this.sharedInboxKeyDispatcher(context);
|
|
6615
|
-
if (identity != null) context = this.#createContext(request, contextData, {
|
|
7699
|
+
if (identity != null) context = this.#createContext(request, contextData, {
|
|
7700
|
+
[outboundProvenance]: getOutboundProvenance(context),
|
|
7701
|
+
documentLoader: "identifier" in identity || "username" in identity ? await context.getDocumentLoader(identity) : context.getDocumentLoader(identity)
|
|
7702
|
+
});
|
|
6616
7703
|
}
|
|
6617
7704
|
return await this.#handleInbox(request, {
|
|
6618
7705
|
recipient,
|
|
@@ -6621,13 +7708,16 @@ var FederationImpl = class extends FederationBuilderImpl {
|
|
|
6621
7708
|
onNotFound,
|
|
6622
7709
|
observation
|
|
6623
7710
|
});
|
|
6624
|
-
}, span);
|
|
7711
|
+
}, span));
|
|
6625
7712
|
}
|
|
6626
7713
|
switch (routeName) {
|
|
6627
7714
|
case "actor":
|
|
6628
7715
|
case "actorAlias": {
|
|
6629
7716
|
const identifier = route.name.startsWith("actorAlias:") ? route.name.substring(ACTOR_ALIAS_PREFIX.length) : route.values.identifier;
|
|
6630
|
-
context = this.#createContext(request, contextData, {
|
|
7717
|
+
context = this.#createContext(request, contextData, {
|
|
7718
|
+
[outboundProvenance]: getOutboundProvenance(context),
|
|
7719
|
+
invokedFromActorDispatcher: { identifier }
|
|
7720
|
+
});
|
|
6631
7721
|
return await handleActor(request, {
|
|
6632
7722
|
identifier,
|
|
6633
7723
|
context,
|
|
@@ -6641,10 +7731,13 @@ var FederationImpl = class extends FederationBuilderImpl {
|
|
|
6641
7731
|
const typeId = route.name.replace(/^object:/, "");
|
|
6642
7732
|
const callbacks = this.objectCallbacks[typeId];
|
|
6643
7733
|
const cls = this.objectTypeIds[typeId];
|
|
6644
|
-
context = this.#createContext(request, contextData, {
|
|
6645
|
-
|
|
6646
|
-
|
|
6647
|
-
|
|
7734
|
+
context = this.#createContext(request, contextData, {
|
|
7735
|
+
[outboundProvenance]: getOutboundProvenance(context),
|
|
7736
|
+
invokedFromObjectDispatcher: {
|
|
7737
|
+
cls,
|
|
7738
|
+
values: route.values
|
|
7739
|
+
}
|
|
7740
|
+
});
|
|
6648
7741
|
return await handleObject(request, {
|
|
6649
7742
|
values: route.values,
|
|
6650
7743
|
context,
|
|
@@ -6702,7 +7795,10 @@ var FederationImpl = class extends FederationBuilderImpl {
|
|
|
6702
7795
|
case "sharedInbox":
|
|
6703
7796
|
if (this.sharedInboxKeyDispatcher != null) {
|
|
6704
7797
|
const identity = await this.sharedInboxKeyDispatcher(context);
|
|
6705
|
-
if (identity != null) context = this.#createContext(request, contextData, {
|
|
7798
|
+
if (identity != null) context = this.#createContext(request, contextData, {
|
|
7799
|
+
[outboundProvenance]: getOutboundProvenance(context),
|
|
7800
|
+
documentLoader: "identifier" in identity || "username" in identity ? await context.getDocumentLoader(identity) : context.getDocumentLoader(identity)
|
|
7801
|
+
});
|
|
6706
7802
|
}
|
|
6707
7803
|
return await this.#handleInbox(request, {
|
|
6708
7804
|
recipient: route.values.identifier ?? null,
|
|
@@ -6846,14 +7942,17 @@ var FederationImpl = class extends FederationBuilderImpl {
|
|
|
6846
7942
|
});
|
|
6847
7943
|
return response;
|
|
6848
7944
|
}
|
|
7945
|
+
const provenance = globalThis.Object.freeze({ originatingRequestId: metricState.requestId });
|
|
6849
7946
|
if (collectionRoute != null) return await this.#fetchPortableCollection(request, portable, route, {
|
|
6850
7947
|
onNotFound,
|
|
6851
7948
|
onUnauthorized,
|
|
6852
|
-
contextData
|
|
7949
|
+
contextData,
|
|
7950
|
+
provenance
|
|
6853
7951
|
});
|
|
6854
7952
|
if (isActor) {
|
|
6855
7953
|
const identifier = route.name.startsWith("actorAlias:") ? route.name.substring(ACTOR_ALIAS_PREFIX.length) : route.values.identifier;
|
|
6856
7954
|
const context = this.#createContext(request, contextData, {
|
|
7955
|
+
[outboundProvenance]: provenance,
|
|
6857
7956
|
invokedFromActorDispatcher: { identifier },
|
|
6858
7957
|
portableRequest: portable.portableRequest
|
|
6859
7958
|
});
|
|
@@ -6874,6 +7973,7 @@ var FederationImpl = class extends FederationBuilderImpl {
|
|
|
6874
7973
|
const callbacks = this.objectCallbacks[typeId];
|
|
6875
7974
|
const cls = this.objectTypeIds[typeId];
|
|
6876
7975
|
const context = this.#createContext(request, contextData, {
|
|
7976
|
+
[outboundProvenance]: provenance,
|
|
6877
7977
|
invokedFromObjectDispatcher: {
|
|
6878
7978
|
cls,
|
|
6879
7979
|
values: route.values
|
|
@@ -6891,7 +7991,7 @@ var FederationImpl = class extends FederationBuilderImpl {
|
|
|
6891
7991
|
onNotFound
|
|
6892
7992
|
});
|
|
6893
7993
|
}
|
|
6894
|
-
async #fetchPortableCollection(request, portable, route, { onNotFound, onUnauthorized, contextData }) {
|
|
7994
|
+
async #fetchPortableCollection(request, portable, route, { onNotFound, onUnauthorized, contextData, provenance }) {
|
|
6895
7995
|
const logger = getLogger([
|
|
6896
7996
|
"fedify",
|
|
6897
7997
|
"federation",
|
|
@@ -6899,8 +7999,11 @@ var FederationImpl = class extends FederationBuilderImpl {
|
|
|
6899
7999
|
]);
|
|
6900
8000
|
const url = new URL(request.url);
|
|
6901
8001
|
const { authority } = portable.portableRequest;
|
|
6902
|
-
const ownerContext = this.#createContext(request, contextData);
|
|
6903
|
-
const context = this.#createContext(request, contextData, {
|
|
8002
|
+
const ownerContext = this.#createContext(request, contextData, { [outboundProvenance]: provenance });
|
|
8003
|
+
const context = this.#createContext(request, contextData, {
|
|
8004
|
+
[outboundProvenance]: provenance,
|
|
8005
|
+
portableRequest: portable.portableRequest
|
|
8006
|
+
});
|
|
6904
8007
|
const reject = async (reason, identifier) => {
|
|
6905
8008
|
logger.debug("Not serving the portable collection {collection} of the actor {identifier}: {reason}.", {
|
|
6906
8009
|
collection: portable.canonicalId,
|
|
@@ -6993,6 +8096,11 @@ var FederationImpl = class extends FederationBuilderImpl {
|
|
|
6993
8096
|
}
|
|
6994
8097
|
return await onNotFound(request);
|
|
6995
8098
|
}
|
|
8099
|
+
#createInboxRequestSource(request, metricState) {
|
|
8100
|
+
const source = createInboxRequestSource(request, metricState.requestId ?? getRequestId(request), this.origin?.webOrigin);
|
|
8101
|
+
metricState.inboxRequest = source;
|
|
8102
|
+
return source;
|
|
8103
|
+
}
|
|
6996
8104
|
#setInboxCompletion(metricState, observation, getContext) {
|
|
6997
8105
|
const handler = this.inboxRequestFinishedHandler;
|
|
6998
8106
|
metricState.inboxCompletion = {
|
|
@@ -7024,6 +8132,7 @@ var FederationImpl = class extends FederationBuilderImpl {
|
|
|
7024
8132
|
async #reportInboxPreparationError(request, contextData, inbox, error, span, metricState) {
|
|
7025
8133
|
const unavailableLoader = () => Promise.reject(error);
|
|
7026
8134
|
const context = new RequestContextImpl({
|
|
8135
|
+
[outboundProvenance]: globalThis.Object.freeze({ inboxRequest: metricState.inboxRequest }),
|
|
7027
8136
|
url: new URL(request.url),
|
|
7028
8137
|
request,
|
|
7029
8138
|
federation: this,
|
|
@@ -7031,7 +8140,7 @@ var FederationImpl = class extends FederationBuilderImpl {
|
|
|
7031
8140
|
documentLoader: unavailableLoader,
|
|
7032
8141
|
contextLoader: unavailableLoader
|
|
7033
8142
|
});
|
|
7034
|
-
const observation = new InboxObservation(inbox);
|
|
8143
|
+
const observation = new InboxObservation(inbox, metricState.inboxRequest);
|
|
7035
8144
|
this.#setInboxCompletion(metricState, observation, () => context);
|
|
7036
8145
|
return await observation.run(() => Promise.reject(error), span);
|
|
7037
8146
|
}
|
|
@@ -7089,9 +8198,10 @@ var FederationImpl = class extends FederationBuilderImpl {
|
|
|
7089
8198
|
metricState.endpoint = "inbox";
|
|
7090
8199
|
span.updateName(`${request.method} ${metricState.routeTemplate}`);
|
|
7091
8200
|
const identifier = route.values.identifier;
|
|
8201
|
+
const source = this.#createInboxRequestSource(request, metricState);
|
|
7092
8202
|
let context;
|
|
7093
8203
|
try {
|
|
7094
|
-
context = this.#createContext(request, contextData);
|
|
8204
|
+
context = withContext({ inboxRequestId: source.inboxRequestId }, () => this.#createContext(request, contextData, { [outboundProvenance]: globalThis.Object.freeze({ inboxRequest: source }) }));
|
|
7095
8205
|
} catch (error) {
|
|
7096
8206
|
return await this.#reportInboxPreparationError(request, contextData, {
|
|
7097
8207
|
kind: "portable",
|
|
@@ -7101,9 +8211,9 @@ var FederationImpl = class extends FederationBuilderImpl {
|
|
|
7101
8211
|
const observation = new InboxObservation({
|
|
7102
8212
|
kind: "portable",
|
|
7103
8213
|
recipient: identifier
|
|
7104
|
-
});
|
|
8214
|
+
}, source);
|
|
7105
8215
|
this.#setInboxCompletion(metricState, observation, () => context);
|
|
7106
|
-
return await observation.run(async () => {
|
|
8216
|
+
return await withContext({ inboxRequestId: source.inboxRequestId }, () => observation.run(async () => {
|
|
7107
8217
|
const actor = this.actorCallbacks?.dispatcher == null ? null : await this.actorCallbacks.dispatcher(context, identifier);
|
|
7108
8218
|
const localOrigin = context.canonicalOrigin;
|
|
7109
8219
|
const resolution = resolvePortableInboxRecipient(actor == null || actor instanceof Tombstone ? null : actor, {
|
|
@@ -7120,7 +8230,10 @@ var FederationImpl = class extends FederationBuilderImpl {
|
|
|
7120
8230
|
return await onNotFound(request);
|
|
7121
8231
|
}
|
|
7122
8232
|
const { recipient } = resolution;
|
|
7123
|
-
if (this.actorCallbacks?.keyPairsDispatcher != null) context = this.#createContext(request, contextData, {
|
|
8233
|
+
if (this.actorCallbacks?.keyPairsDispatcher != null) context = this.#createContext(request, contextData, {
|
|
8234
|
+
[outboundProvenance]: getOutboundProvenance(context),
|
|
8235
|
+
documentLoader: await context.getDocumentLoader({ identifier })
|
|
8236
|
+
});
|
|
7124
8237
|
const excludedOrigins = [localOrigin, new URL(request.url).origin];
|
|
7125
8238
|
return await this.#handleInbox(request, {
|
|
7126
8239
|
recipient: identifier,
|
|
@@ -7130,16 +8243,25 @@ var FederationImpl = class extends FederationBuilderImpl {
|
|
|
7130
8243
|
onNotFound,
|
|
7131
8244
|
portableInbox: {
|
|
7132
8245
|
recipient,
|
|
7133
|
-
forward:
|
|
7134
|
-
|
|
8246
|
+
forward: (activity, activityId, activityType, signatures) => observeOutboundBroadcast(context, this.outboundObservers, async (observation) => {
|
|
8247
|
+
observation?.setActivity({
|
|
8248
|
+
...activityId == null ? {} : { id: activityId.href },
|
|
8249
|
+
type: activityType
|
|
8250
|
+
});
|
|
8251
|
+
if (this.portableInboxForwarding.maxTargets < 1) {
|
|
8252
|
+
if (observation != null) await observation.skipped("forwarding-disabled");
|
|
8253
|
+
return;
|
|
8254
|
+
}
|
|
7135
8255
|
if (this.kv.cas == null) {
|
|
7136
8256
|
if (!this.#portableInboxForwardingWarned) {
|
|
7137
8257
|
this.#portableInboxForwardingWarned = true;
|
|
7138
8258
|
logger.warn("Activities delivered to FEP-ef61 portable inboxes are not forwarded to the other gateways, as the key–value store does not support compare-and-swap (KvStore.cas()), which is needed to forward each activity at most once.");
|
|
7139
8259
|
}
|
|
8260
|
+
if (observation != null) await observation.skipped("forwarding-unavailable");
|
|
7140
8261
|
return;
|
|
7141
8262
|
}
|
|
7142
8263
|
await forwardPortableInboxActivity({
|
|
8264
|
+
provenance: getOutboundProvenance(context),
|
|
7143
8265
|
recipient,
|
|
7144
8266
|
activity,
|
|
7145
8267
|
activityId,
|
|
@@ -7157,11 +8279,11 @@ var FederationImpl = class extends FederationBuilderImpl {
|
|
|
7157
8279
|
options: this.portableInboxForwarding,
|
|
7158
8280
|
meterProvider: this.meterProvider,
|
|
7159
8281
|
tracerProvider: this.tracerProvider
|
|
7160
|
-
});
|
|
7161
|
-
}
|
|
8282
|
+
}, observation);
|
|
8283
|
+
}, "portable-replication")
|
|
7162
8284
|
}
|
|
7163
8285
|
});
|
|
7164
|
-
}, span);
|
|
8286
|
+
}, span));
|
|
7165
8287
|
}
|
|
7166
8288
|
/**
|
|
7167
8289
|
* Gets this server's gateway key pairs for the portable actor that owns
|
|
@@ -7231,7 +8353,7 @@ var FederationImpl = class extends FederationBuilderImpl {
|
|
|
7231
8353
|
headers: { "Content-Type": "text/plain; charset=utf-8" }
|
|
7232
8354
|
});
|
|
7233
8355
|
}
|
|
7234
|
-
const context = this.#createContext(request, contextData);
|
|
8356
|
+
const context = this.#createContext(request, contextData, { [outboundProvenance]: globalThis.Object.freeze({ originatingRequestId: metricState.requestId }) });
|
|
7235
8357
|
const response = await this.hashlinkMediaDispatcher(context, media.media);
|
|
7236
8358
|
if (response == null) return await onNotFound(request);
|
|
7237
8359
|
return await discardBodyForHead(request, response, false);
|
|
@@ -7373,8 +8495,9 @@ var ContextImpl = class ContextImpl {
|
|
|
7373
8495
|
verifyPortableObject;
|
|
7374
8496
|
invokedFromActorKeyPairsDispatcher;
|
|
7375
8497
|
#codec;
|
|
7376
|
-
constructor({ url, federation, data, documentLoader, contextLoader, invokedFromActorKeyPairsDispatcher }) {
|
|
8498
|
+
constructor({ url, federation, data, documentLoader, contextLoader, invokedFromActorKeyPairsDispatcher, [outboundProvenance]: provenance = emptyProvenance }) {
|
|
7377
8499
|
this.url = url;
|
|
8500
|
+
globalThis.Object.assign(this, { [outboundProvenance]: provenance });
|
|
7378
8501
|
this.federation = federation;
|
|
7379
8502
|
this.data = data;
|
|
7380
8503
|
this.documentLoader = documentLoader;
|
|
@@ -7396,6 +8519,7 @@ var ContextImpl = class ContextImpl {
|
|
|
7396
8519
|
}
|
|
7397
8520
|
clone(data) {
|
|
7398
8521
|
return new ContextImpl({
|
|
8522
|
+
[outboundProvenance]: getOutboundProvenance(this),
|
|
7399
8523
|
url: this.url,
|
|
7400
8524
|
federation: this.federation,
|
|
7401
8525
|
data,
|
|
@@ -7406,6 +8530,7 @@ var ContextImpl = class ContextImpl {
|
|
|
7406
8530
|
}
|
|
7407
8531
|
toInboxContext(recipient, activity, activityId, activityType, metadata) {
|
|
7408
8532
|
return new InboxContextImpl(recipient, activity, activityId, activityType, {
|
|
8533
|
+
[outboundProvenance]: getOutboundProvenance(this),
|
|
7409
8534
|
url: this.url,
|
|
7410
8535
|
federation: this.federation,
|
|
7411
8536
|
data: this.data,
|
|
@@ -7416,6 +8541,7 @@ var ContextImpl = class ContextImpl {
|
|
|
7416
8541
|
}
|
|
7417
8542
|
toOutboxContext(identifier, activity, activityId, activityType) {
|
|
7418
8543
|
return new OutboxContextImpl(identifier, activity, activityId, activityType, {
|
|
8544
|
+
[outboundProvenance]: getOutboundProvenance(this),
|
|
7419
8545
|
url: this.url,
|
|
7420
8546
|
federation: this.federation,
|
|
7421
8547
|
data: this.data,
|
|
@@ -7805,36 +8931,49 @@ var ContextImpl = class ContextImpl {
|
|
|
7805
8931
|
await this.#enqueueTasks(task, payloads, options);
|
|
7806
8932
|
}
|
|
7807
8933
|
sendActivity(sender, recipients, activity, options = {}) {
|
|
7808
|
-
return this
|
|
7809
|
-
|
|
7810
|
-
|
|
7811
|
-
|
|
7812
|
-
|
|
7813
|
-
|
|
7814
|
-
|
|
7815
|
-
|
|
7816
|
-
|
|
7817
|
-
|
|
7818
|
-
|
|
7819
|
-
|
|
7820
|
-
|
|
7821
|
-
|
|
7822
|
-
|
|
7823
|
-
|
|
7824
|
-
|
|
7825
|
-
|
|
7826
|
-
|
|
7827
|
-
|
|
7828
|
-
|
|
7829
|
-
|
|
7830
|
-
|
|
8934
|
+
return observeOutboundBroadcast(this, this.federation.outboundObservers, (observation, provenance) => {
|
|
8935
|
+
const tracer = this.tracerProvider.getTracer(name, version);
|
|
8936
|
+
const parentSpanContext = trace.getSpanContext(context.active());
|
|
8937
|
+
const activityType = getTypeId(activity).href;
|
|
8938
|
+
observation?.setActivity({ type: activityType });
|
|
8939
|
+
return tracer.startActiveSpan(this.federation.outboxQueue == null || options.immediate ? "activitypub.outbox" : "activitypub.fanout", {
|
|
8940
|
+
kind: this.federation.outboxQueue == null || options.immediate ? SpanKind.CLIENT : SpanKind.PRODUCER,
|
|
8941
|
+
attributes: {
|
|
8942
|
+
"activitypub.activity.type": activityType,
|
|
8943
|
+
"activitypub.activity.to": activity.toIds.map((to) => to.href),
|
|
8944
|
+
"activitypub.activity.cc": activity.ccIds.map((cc) => cc.href),
|
|
8945
|
+
"activitypub.activity.bto": activity.btoIds.map((bto) => bto.href),
|
|
8946
|
+
"activitypub.activity.bcc": activity.bccIds.map((bcc) => bcc.href)
|
|
8947
|
+
}
|
|
8948
|
+
}, async (span) => {
|
|
8949
|
+
observation?.setSpan(span, parentSpanContext);
|
|
8950
|
+
try {
|
|
8951
|
+
const activityId = activity.id?.href;
|
|
8952
|
+
if (activityId != null) span.setAttribute("activitypub.activity.id", activityId);
|
|
8953
|
+
observation?.setActivity({
|
|
8954
|
+
type: activityType,
|
|
8955
|
+
...activityId == null ? {} : { id: activityId }
|
|
8956
|
+
});
|
|
8957
|
+
await this.sendActivityInternal(sender, recipients, activity, options, span, observation, provenance);
|
|
8958
|
+
} catch (e) {
|
|
8959
|
+
span.setStatus({
|
|
8960
|
+
code: SpanStatusCode.ERROR,
|
|
8961
|
+
message: String(e)
|
|
8962
|
+
});
|
|
8963
|
+
throw e;
|
|
8964
|
+
} finally {
|
|
8965
|
+
span.end();
|
|
8966
|
+
}
|
|
8967
|
+
});
|
|
8968
|
+
}, "send", options.dispatchId);
|
|
7831
8969
|
}
|
|
7832
|
-
async sendActivityInternal(sender, recipients, activity, options, span) {
|
|
8970
|
+
async sendActivityInternal(sender, recipients, activity, options, span, observation, provenance = getOutboundProvenance(this)) {
|
|
7833
8971
|
const logger = getLogger([
|
|
7834
8972
|
"fedify",
|
|
7835
8973
|
"federation",
|
|
7836
8974
|
"outbox"
|
|
7837
8975
|
]);
|
|
8976
|
+
observation?.setPhase("sender-resolution");
|
|
7838
8977
|
let keys;
|
|
7839
8978
|
let identifier = null;
|
|
7840
8979
|
let actorKeyPairs = null;
|
|
@@ -7850,6 +8989,7 @@ var ContextImpl = class ContextImpl {
|
|
|
7850
8989
|
}
|
|
7851
8990
|
}
|
|
7852
8991
|
span.setAttribute("fedify.actor.identifier", identifier);
|
|
8992
|
+
observation?.setPhase("key-resolution");
|
|
7853
8993
|
if (this.federation.actorCallbacks?.keyPairsDispatcher == null) throw new Error("No actor key pairs dispatcher registered.");
|
|
7854
8994
|
actorKeyPairs = await this.getActorKeyPairs(identifier);
|
|
7855
8995
|
if (actorKeyPairs.length < 1) throw new Error(`No key pair found for actor ${JSON.stringify(identifier)}.`);
|
|
@@ -7858,11 +8998,14 @@ var ContextImpl = class ContextImpl {
|
|
|
7858
8998
|
privateKey: kp.privateKey
|
|
7859
8999
|
}));
|
|
7860
9000
|
} else if (Array.isArray(sender)) {
|
|
9001
|
+
observation?.setPhase("key-resolution");
|
|
7861
9002
|
if (sender.length < 1) throw new Error("The sender's key pairs are empty.");
|
|
7862
9003
|
keys = sender;
|
|
7863
9004
|
} else keys = [sender];
|
|
7864
9005
|
if (keys.length < 1) throw new TypeError("The sender's keys must not be empty.");
|
|
9006
|
+
observation?.setPhase("key-resolution");
|
|
7865
9007
|
for (const { privateKey } of keys) validateCryptoKey(privateKey, "private");
|
|
9008
|
+
observation?.setPhase("recipient-resolution");
|
|
7866
9009
|
let expandedRecipients;
|
|
7867
9010
|
let collectionSync;
|
|
7868
9011
|
if (Array.isArray(recipients)) expandedRecipients = recipients;
|
|
@@ -7878,14 +9021,34 @@ var ContextImpl = class ContextImpl {
|
|
|
7878
9021
|
} else expandedRecipients = [recipients];
|
|
7879
9022
|
const opts = {
|
|
7880
9023
|
context: this,
|
|
9024
|
+
observation,
|
|
9025
|
+
provenance,
|
|
7881
9026
|
orderingKey: options.orderingKey,
|
|
7882
9027
|
collectionSync,
|
|
7883
9028
|
immediate: options.immediate,
|
|
7884
9029
|
normalizeExistingProofs: options.normalizeExistingProofs
|
|
7885
9030
|
};
|
|
7886
9031
|
span.setAttribute("activitypub.inboxes", expandedRecipients.length);
|
|
9032
|
+
observation?.setPhase("transformation");
|
|
9033
|
+
if (this.federation.activityTransformers.length > 0) observation?.setActivity({});
|
|
7887
9034
|
for (const activityTransformer of this.federation.activityTransformers) activity = activityTransformer(activity, this);
|
|
7888
|
-
|
|
9035
|
+
let transformedActivityId;
|
|
9036
|
+
try {
|
|
9037
|
+
transformedActivityId = activity?.id?.href;
|
|
9038
|
+
} finally {
|
|
9039
|
+
if (observation != null) {
|
|
9040
|
+
let activityType;
|
|
9041
|
+
try {
|
|
9042
|
+
activityType = getTypeId(activity)?.href;
|
|
9043
|
+
} catch {}
|
|
9044
|
+
observation.setActivity({
|
|
9045
|
+
...transformedActivityId == null ? {} : { id: transformedActivityId },
|
|
9046
|
+
...activityType == null ? {} : { type: activityType }
|
|
9047
|
+
});
|
|
9048
|
+
}
|
|
9049
|
+
}
|
|
9050
|
+
span?.setAttribute("activitypub.activity.id", transformedActivityId ?? "");
|
|
9051
|
+
observation?.setPhase("validation");
|
|
7889
9052
|
if (activity.actorId == null) {
|
|
7890
9053
|
logger.error("Activity {activityId} to send does not have an actor.", {
|
|
7891
9054
|
activity,
|
|
@@ -7893,6 +9056,7 @@ var ContextImpl = class ContextImpl {
|
|
|
7893
9056
|
});
|
|
7894
9057
|
throw new TypeError("The activity to send must have at least one actor property.");
|
|
7895
9058
|
}
|
|
9059
|
+
observation?.setPhase("signing");
|
|
7896
9060
|
const { activity: signedActivity, proofCreated } = await signOutgoingActivity(activity, actorKeyPairs == null ? keys.map(({ keyId, privateKey }) => ({
|
|
7897
9061
|
verificationMethod: keyId,
|
|
7898
9062
|
privateKey
|
|
@@ -7905,16 +9069,19 @@ var ContextImpl = class ContextImpl {
|
|
|
7905
9069
|
appendToExistingProofs: actorKeyPairs != null
|
|
7906
9070
|
});
|
|
7907
9071
|
activity = signedActivity;
|
|
9072
|
+
observation?.setPhase("destination-planning");
|
|
7908
9073
|
const inboxes = extractInboxes({
|
|
7909
9074
|
recipients: expandedRecipients,
|
|
7910
9075
|
preferSharedInbox: options.preferSharedInbox,
|
|
7911
9076
|
excludeBaseUris: options.excludeBaseUris
|
|
7912
9077
|
});
|
|
9078
|
+
if (observation != null) await observation.planDestinations(inboxes);
|
|
7913
9079
|
if (globalThis.Object.keys(inboxes).length < 1) {
|
|
7914
9080
|
logger.debug("No inboxes found for activity {activityId}.", {
|
|
7915
9081
|
activityId: activity.id?.href,
|
|
7916
9082
|
activity
|
|
7917
9083
|
});
|
|
9084
|
+
if (observation != null) await observation.skipped();
|
|
7918
9085
|
return false;
|
|
7919
9086
|
}
|
|
7920
9087
|
logger.debug("Sending activity {activityId} to inboxes:\n{inboxes}", {
|
|
@@ -7922,6 +9089,7 @@ var ContextImpl = class ContextImpl {
|
|
|
7922
9089
|
activityId: activity.id?.href,
|
|
7923
9090
|
activity
|
|
7924
9091
|
});
|
|
9092
|
+
observation?.setPhase("serialization");
|
|
7925
9093
|
const activityJsonLd = await activity.toJsonLd({
|
|
7926
9094
|
format: "compact",
|
|
7927
9095
|
contextLoader: this.contextLoader
|
|
@@ -7942,33 +9110,48 @@ var ContextImpl = class ContextImpl {
|
|
|
7942
9110
|
});
|
|
7943
9111
|
return true;
|
|
7944
9112
|
}
|
|
9113
|
+
observation?.setPhase("validation");
|
|
7945
9114
|
assertSupportedCompoundProofShape(activityJsonLd, activity.id?.href);
|
|
9115
|
+
observation?.setPhase("key-resolution");
|
|
7946
9116
|
const keyJwkPairs = await Promise.all(keys.map(async ({ keyId, privateKey }) => ({
|
|
7947
9117
|
keyId: keyId.href,
|
|
7948
9118
|
privateKey: await exportJwk(privateKey)
|
|
7949
9119
|
})));
|
|
9120
|
+
observation?.setPhase("request-preparation");
|
|
7950
9121
|
const carrier = {};
|
|
7951
9122
|
propagation.inject(context.active(), carrier);
|
|
9123
|
+
const activityId = activity.id?.href;
|
|
9124
|
+
const activityType = getTypeId(activity).href;
|
|
9125
|
+
observation?.setActivity({
|
|
9126
|
+
...activityId == null ? {} : { id: activityId },
|
|
9127
|
+
type: activityType
|
|
9128
|
+
});
|
|
7952
9129
|
const message = {
|
|
7953
9130
|
type: "fanout",
|
|
9131
|
+
...serializeProvenance(provenance),
|
|
7954
9132
|
id: crypto.randomUUID(),
|
|
7955
9133
|
baseUrl: this.origin,
|
|
7956
9134
|
keys: keyJwkPairs,
|
|
7957
9135
|
inboxes: globalThis.Object.fromEntries(globalThis.Object.entries(inboxes).map(([k, { actorIds, sharedInbox, portableInbox, gatewayInboxes }]) => [k, {
|
|
7958
9136
|
actorIds: [...actorIds],
|
|
9137
|
+
...observation?.continuation(k),
|
|
7959
9138
|
sharedInbox,
|
|
7960
9139
|
...getPortableMessageFields(portableInbox, gatewayInboxes)
|
|
7961
9140
|
}])),
|
|
7962
9141
|
activity: activityJsonLd,
|
|
7963
|
-
activityId
|
|
7964
|
-
activityType
|
|
9142
|
+
activityId,
|
|
9143
|
+
activityType,
|
|
7965
9144
|
collectionSync: opts.collectionSync,
|
|
7966
9145
|
orderingKey: options.orderingKey,
|
|
7967
9146
|
normalizeExistingProofs: proofCreated || options.normalizeExistingProofs,
|
|
7968
9147
|
traceContext: carrier
|
|
7969
9148
|
};
|
|
7970
9149
|
if (!this.federation.manuallyStartQueue) this.federation._startQueueInternal(this.data);
|
|
7971
|
-
|
|
9150
|
+
if (observation != null) await observation.prepared("fanout");
|
|
9151
|
+
await (observation == null ? this.federation.fanoutQueue.enqueue(message, { orderingKey: options.orderingKey }) : observation.enqueue(globalThis.Object.keys(inboxes).map((inbox) => ({
|
|
9152
|
+
inbox,
|
|
9153
|
+
queueMessageId: message.id
|
|
9154
|
+
})), "fanout", () => this.federation.fanoutQueue.enqueue(message, { orderingKey: options.orderingKey })));
|
|
7972
9155
|
getFederationMetrics(this.federation.meterProvider).recordQueueTaskEnqueued({
|
|
7973
9156
|
role: "fanout",
|
|
7974
9157
|
queue: this.federation.fanoutQueue,
|
|
@@ -8229,6 +9412,7 @@ var RequestContextImpl = class RequestContextImpl extends ContextImpl {
|
|
|
8229
9412
|
}
|
|
8230
9413
|
clone(data) {
|
|
8231
9414
|
return new RequestContextImpl({
|
|
9415
|
+
[outboundProvenance]: getOutboundProvenance(this),
|
|
8232
9416
|
url: this.url,
|
|
8233
9417
|
federation: this.federation,
|
|
8234
9418
|
data,
|
|
@@ -8400,30 +9584,40 @@ var RequestContextImpl = class RequestContextImpl extends ContextImpl {
|
|
|
8400
9584
|
}
|
|
8401
9585
|
};
|
|
8402
9586
|
function forwardActivity(ctx, loggerCategory, forwarder, recipients, options) {
|
|
8403
|
-
return ctx
|
|
8404
|
-
|
|
8405
|
-
|
|
8406
|
-
|
|
8407
|
-
|
|
8408
|
-
|
|
8409
|
-
|
|
8410
|
-
}
|
|
8411
|
-
|
|
8412
|
-
|
|
8413
|
-
|
|
8414
|
-
|
|
8415
|
-
|
|
8416
|
-
|
|
8417
|
-
|
|
8418
|
-
|
|
8419
|
-
|
|
9587
|
+
return observeOutboundBroadcast(ctx, ctx.federation.outboundObservers, (observation, provenance) => {
|
|
9588
|
+
observation?.setActivity({
|
|
9589
|
+
...ctx.activityId == null ? {} : { id: ctx.activityId },
|
|
9590
|
+
type: ctx.activityType
|
|
9591
|
+
});
|
|
9592
|
+
const tracer = ctx.tracerProvider.getTracer(name, version);
|
|
9593
|
+
const parentSpanContext = trace.getSpanContext(context.active());
|
|
9594
|
+
return tracer.startActiveSpan(ctx.federation.outboxQueue == null || options?.immediate ? `activitypub.${loggerCategory}` : "activitypub.fanout", {
|
|
9595
|
+
kind: ctx.federation.outboxQueue == null || options?.immediate ? SpanKind.CLIENT : SpanKind.PRODUCER,
|
|
9596
|
+
attributes: { "activitypub.activity.type": ctx.activityType }
|
|
9597
|
+
}, async (span) => {
|
|
9598
|
+
observation?.setSpan(span, parentSpanContext);
|
|
9599
|
+
try {
|
|
9600
|
+
if (ctx.activityId != null) span.setAttribute("activitypub.activity.id", ctx.activityId);
|
|
9601
|
+
return await forwardActivityInternal(ctx, loggerCategory, forwarder, recipients, options, observation, provenance);
|
|
9602
|
+
} catch (e) {
|
|
9603
|
+
span.setStatus({
|
|
9604
|
+
code: SpanStatusCode.ERROR,
|
|
9605
|
+
message: String(e)
|
|
9606
|
+
});
|
|
9607
|
+
throw e;
|
|
9608
|
+
} finally {
|
|
9609
|
+
span.end();
|
|
9610
|
+
}
|
|
9611
|
+
});
|
|
9612
|
+
}, "forward", options?.dispatchId);
|
|
8420
9613
|
}
|
|
8421
|
-
async function forwardActivityInternal(ctx, loggerCategory, forwarder, recipients, options) {
|
|
9614
|
+
async function forwardActivityInternal(ctx, loggerCategory, forwarder, recipients, options, observation, provenance = getOutboundProvenance(ctx)) {
|
|
8422
9615
|
const logger = getLogger([
|
|
8423
9616
|
"fedify",
|
|
8424
9617
|
"federation",
|
|
8425
9618
|
loggerCategory
|
|
8426
9619
|
]);
|
|
9620
|
+
observation?.setPhase("sender-resolution");
|
|
8427
9621
|
let keys;
|
|
8428
9622
|
let identifier = null;
|
|
8429
9623
|
if ("identifier" in forwarder || "username" in forwarder) {
|
|
@@ -8437,6 +9631,7 @@ async function forwardActivityInternal(ctx, loggerCategory, forwarder, recipient
|
|
|
8437
9631
|
identifier = mapped;
|
|
8438
9632
|
}
|
|
8439
9633
|
}
|
|
9634
|
+
observation?.setPhase("key-resolution");
|
|
8440
9635
|
const actorKeyPairs = await ctx.getActorKeyPairs(identifier);
|
|
8441
9636
|
if (actorKeyPairs.length < 1) throw new Error(`No key pair found for actor ${JSON.stringify(identifier)}.`);
|
|
8442
9637
|
keys = actorKeyPairs.map((kp) => ({
|
|
@@ -8444,12 +9639,17 @@ async function forwardActivityInternal(ctx, loggerCategory, forwarder, recipient
|
|
|
8444
9639
|
privateKey: kp.privateKey
|
|
8445
9640
|
}));
|
|
8446
9641
|
} else if (Array.isArray(forwarder)) {
|
|
9642
|
+
observation?.setPhase("key-resolution");
|
|
8447
9643
|
if (forwarder.length < 1) throw new Error("The forwarder's key pairs are empty.");
|
|
8448
9644
|
keys = forwarder;
|
|
8449
9645
|
} else keys = [forwarder];
|
|
9646
|
+
observation?.setPhase("validation");
|
|
8450
9647
|
if (!hasSignatureLike(ctx.activity)) {
|
|
8451
9648
|
if (!hasProofLike(ctx.activity)) {
|
|
8452
|
-
if (options?.skipIfUnsigned)
|
|
9649
|
+
if (options?.skipIfUnsigned) {
|
|
9650
|
+
if (observation != null) await observation.skipped("unsigned");
|
|
9651
|
+
return false;
|
|
9652
|
+
}
|
|
8453
9653
|
logger.warn("The activity {activityId} is not signed; even if it is forwarded to other servers as is, it may not be accepted by them due to the lack of a signature/proof.", {
|
|
8454
9654
|
activityId: ctx.activityId,
|
|
8455
9655
|
activityType: ctx.activityType,
|
|
@@ -8457,23 +9657,27 @@ async function forwardActivityInternal(ctx, loggerCategory, forwarder, recipient
|
|
|
8457
9657
|
});
|
|
8458
9658
|
}
|
|
8459
9659
|
}
|
|
9660
|
+
observation?.setPhase("recipient-resolution");
|
|
8460
9661
|
if (recipients === "followers") {
|
|
8461
9662
|
if (identifier == null) throw new Error("If recipients is \"followers\", forwarder must be an actor identifier or username.");
|
|
8462
9663
|
const followers = [];
|
|
8463
9664
|
for await (const recipient of ctx.getFollowers(identifier)) followers.push(recipient);
|
|
8464
9665
|
recipients = followers;
|
|
8465
9666
|
}
|
|
9667
|
+
observation?.setPhase("destination-planning");
|
|
8466
9668
|
const inboxes = extractInboxes({
|
|
8467
9669
|
recipients: Array.isArray(recipients) ? recipients : [recipients],
|
|
8468
9670
|
preferSharedInbox: options?.preferSharedInbox,
|
|
8469
9671
|
excludeBaseUris: options?.excludeBaseUris
|
|
8470
9672
|
});
|
|
9673
|
+
if (observation != null) await observation.planDestinations(inboxes);
|
|
8471
9674
|
if (globalThis.Object.keys(inboxes).length < 1) {
|
|
8472
9675
|
logger.debug("No inboxes found for activity {activityId}.", {
|
|
8473
9676
|
activityId: ctx.activityId,
|
|
8474
9677
|
activityType: ctx.activityType,
|
|
8475
9678
|
identifier: identifier ?? void 0
|
|
8476
9679
|
});
|
|
9680
|
+
if (observation != null) await observation.skipped();
|
|
8477
9681
|
return false;
|
|
8478
9682
|
}
|
|
8479
9683
|
logger.debug("Forwarding activity {activityId} to inboxes:\n{inboxes}", {
|
|
@@ -8484,18 +9688,33 @@ async function forwardActivityInternal(ctx, loggerCategory, forwarder, recipient
|
|
|
8484
9688
|
if (options?.immediate || ctx.federation.outboxQueue == null) {
|
|
8485
9689
|
if (options?.immediate) logger.debug("Forwarding activity immediately without queue since immediate option is set.");
|
|
8486
9690
|
else logger.debug("Forwarding activity immediately without queue since queue is not set.");
|
|
9691
|
+
observation?.setPhase("request-preparation");
|
|
8487
9692
|
const promises = [];
|
|
8488
|
-
for (const inbox in inboxes)
|
|
8489
|
-
|
|
8490
|
-
|
|
8491
|
-
|
|
8492
|
-
|
|
8493
|
-
|
|
8494
|
-
|
|
8495
|
-
|
|
8496
|
-
|
|
8497
|
-
|
|
8498
|
-
|
|
9693
|
+
for (const inbox in inboxes) {
|
|
9694
|
+
const delivery = observation?.delivery(inbox);
|
|
9695
|
+
let sending;
|
|
9696
|
+
try {
|
|
9697
|
+
sending = sendActivityToInbox(inbox, inboxes[inbox].gatewayInboxes, {
|
|
9698
|
+
observation: delivery,
|
|
9699
|
+
allowPrivateAddress: ctx.federation.allowPrivateAddress,
|
|
9700
|
+
keys,
|
|
9701
|
+
activity: ctx.activity,
|
|
9702
|
+
activityId: ctx.activityId,
|
|
9703
|
+
activityType: ctx.activityType,
|
|
9704
|
+
sharedInbox: inboxes[inbox].sharedInbox,
|
|
9705
|
+
meterProvider: ctx.meterProvider,
|
|
9706
|
+
tracerProvider: ctx.tracerProvider,
|
|
9707
|
+
specDeterminer: new KvSpecDeterminer(ctx.federation.kv, ctx.federation.kvPrefixes.httpMessageSignaturesSpec, ctx.federation.firstKnock, { specTtl: ctx.federation.httpMessageSignaturesSpecTtl })
|
|
9708
|
+
});
|
|
9709
|
+
} catch (error) {
|
|
9710
|
+
if (delivery != null) await delivery.failed(error);
|
|
9711
|
+
throw error;
|
|
9712
|
+
}
|
|
9713
|
+
if (observation != null) sending.catch(() => {});
|
|
9714
|
+
if (delivery != null) await delivery.handoff({ type: "direct" });
|
|
9715
|
+
promises.push(sending);
|
|
9716
|
+
}
|
|
9717
|
+
if (observation != null) await observation.prepared("delivery");
|
|
8499
9718
|
await Promise.all(promises);
|
|
8500
9719
|
return true;
|
|
8501
9720
|
}
|
|
@@ -8503,7 +9722,9 @@ async function forwardActivityInternal(ctx, loggerCategory, forwarder, recipient
|
|
|
8503
9722
|
activityId: ctx.activityId,
|
|
8504
9723
|
activity: ctx.activity
|
|
8505
9724
|
});
|
|
9725
|
+
observation?.setPhase("request-preparation");
|
|
8506
9726
|
if (!ctx.federation.manuallyStartQueue) ctx.federation._startQueueInternal(ctx.data);
|
|
9727
|
+
observation?.setPhase("key-resolution");
|
|
8507
9728
|
const keyJwkPairs = [];
|
|
8508
9729
|
for (const { keyId, privateKey } of keys) {
|
|
8509
9730
|
const privateKeyJwk = await exportJwk(privateKey);
|
|
@@ -8512,15 +9733,17 @@ async function forwardActivityInternal(ctx, loggerCategory, forwarder, recipient
|
|
|
8512
9733
|
privateKey: privateKeyJwk
|
|
8513
9734
|
});
|
|
8514
9735
|
}
|
|
9736
|
+
observation?.setPhase("request-preparation");
|
|
8515
9737
|
const carrier = {};
|
|
8516
9738
|
propagation.inject(context.active(), carrier);
|
|
8517
9739
|
const orderingKey = options?.orderingKey;
|
|
8518
9740
|
const started = (/* @__PURE__ */ new Date()).toISOString();
|
|
8519
9741
|
const messages = [];
|
|
8520
|
-
for (const inbox in inboxes) {
|
|
9742
|
+
for (const inbox in inboxes) try {
|
|
8521
9743
|
const { portableInbox, gatewayInboxes } = inboxes[inbox];
|
|
8522
9744
|
const message = {
|
|
8523
9745
|
type: "outbox",
|
|
9746
|
+
...serializeProvenance(provenance),
|
|
8524
9747
|
id: crypto.randomUUID(),
|
|
8525
9748
|
baseUrl: ctx.origin,
|
|
8526
9749
|
keys: keyJwkPairs,
|
|
@@ -8528,6 +9751,7 @@ async function forwardActivityInternal(ctx, loggerCategory, forwarder, recipient
|
|
|
8528
9751
|
activityId: ctx.activityId,
|
|
8529
9752
|
activityType: ctx.activityType,
|
|
8530
9753
|
inbox,
|
|
9754
|
+
...observation?.continuation(inbox),
|
|
8531
9755
|
sharedInbox: inboxes[inbox].sharedInbox,
|
|
8532
9756
|
actorIds: [...inboxes[inbox].actorIds],
|
|
8533
9757
|
started,
|
|
@@ -8541,11 +9765,18 @@ async function forwardActivityInternal(ctx, loggerCategory, forwarder, recipient
|
|
|
8541
9765
|
message,
|
|
8542
9766
|
orderingKey: message.orderingKey
|
|
8543
9767
|
});
|
|
9768
|
+
} catch (error) {
|
|
9769
|
+
if (observation != null) await observation.delivery(inbox)?.failed(error);
|
|
9770
|
+
throw error;
|
|
8544
9771
|
}
|
|
9772
|
+
if (observation != null) await observation.prepared("delivery");
|
|
8545
9773
|
const { outboxQueue } = ctx.federation;
|
|
8546
9774
|
if (outboxQueue.enqueueMany == null || orderingKey != null) {
|
|
8547
9775
|
const errors = (await Promise.allSettled(messages.map(async (m) => {
|
|
8548
|
-
await outboxQueue.enqueue(m.message, { orderingKey: m.orderingKey })
|
|
9776
|
+
await (observation == null ? outboxQueue.enqueue(m.message, { orderingKey: m.orderingKey }) : observation.enqueue([{
|
|
9777
|
+
inbox: m.message.inbox,
|
|
9778
|
+
queueMessageId: m.message.id
|
|
9779
|
+
}], "outbox", () => outboxQueue.enqueue(m.message, { orderingKey: m.orderingKey })));
|
|
8549
9780
|
recordOutboxEnqueue(ctx.federation.meterProvider, outboxQueue, m.message);
|
|
8550
9781
|
}))).filter((r) => r.status === "rejected").map((r) => r.reason);
|
|
8551
9782
|
if (errors.length > 0) {
|
|
@@ -8558,7 +9789,10 @@ async function forwardActivityInternal(ctx, loggerCategory, forwarder, recipient
|
|
|
8558
9789
|
}
|
|
8559
9790
|
} else {
|
|
8560
9791
|
try {
|
|
8561
|
-
await outboxQueue.enqueueMany(messages.map((m) => m.message))
|
|
9792
|
+
await (observation == null ? outboxQueue.enqueueMany(messages.map((m) => m.message)) : observation.enqueue(messages.map(({ message }) => ({
|
|
9793
|
+
inbox: message.inbox,
|
|
9794
|
+
queueMessageId: message.id
|
|
9795
|
+
})), "outbox", () => outboxQueue.enqueueMany(messages.map((m) => m.message))));
|
|
8562
9796
|
} catch (error) {
|
|
8563
9797
|
logger.error("Failed to enqueue activity {activityId} to forward later:\n{error}", {
|
|
8564
9798
|
activityId: ctx.activityId,
|
|
@@ -8594,6 +9828,7 @@ var InboxContextImpl = class InboxContextImpl extends ContextImpl {
|
|
|
8594
9828
|
}
|
|
8595
9829
|
clone(data) {
|
|
8596
9830
|
return new InboxContextImpl(this.recipient, this.activity, this.activityId, this.activityType, {
|
|
9831
|
+
[outboundProvenance]: getOutboundProvenance(this),
|
|
8597
9832
|
url: this.url,
|
|
8598
9833
|
federation: this.federation,
|
|
8599
9834
|
data,
|
|
@@ -8624,29 +9859,41 @@ var OutboxContextImpl = class OutboxContextImpl extends ContextImpl {
|
|
|
8624
9859
|
return this.#deliveryState.delivered;
|
|
8625
9860
|
}
|
|
8626
9861
|
sendActivity(sender, recipients, activity, options = {}) {
|
|
8627
|
-
return this
|
|
8628
|
-
|
|
8629
|
-
|
|
8630
|
-
|
|
8631
|
-
|
|
8632
|
-
|
|
8633
|
-
|
|
8634
|
-
|
|
8635
|
-
|
|
8636
|
-
|
|
8637
|
-
|
|
8638
|
-
|
|
8639
|
-
|
|
8640
|
-
|
|
8641
|
-
|
|
8642
|
-
|
|
8643
|
-
|
|
8644
|
-
|
|
8645
|
-
|
|
8646
|
-
|
|
8647
|
-
|
|
8648
|
-
|
|
8649
|
-
|
|
9862
|
+
return observeOutboundBroadcast(this, this.federation.outboundObservers, (observation, provenance) => {
|
|
9863
|
+
const tracer = this.tracerProvider.getTracer(name, version);
|
|
9864
|
+
const parentSpanContext = trace.getSpanContext(context.active());
|
|
9865
|
+
const activityType = getTypeId(activity).href;
|
|
9866
|
+
observation?.setActivity({ type: activityType });
|
|
9867
|
+
return tracer.startActiveSpan(this.federation.outboxQueue == null || options.immediate ? "activitypub.outbox" : "activitypub.fanout", {
|
|
9868
|
+
kind: this.federation.outboxQueue == null || options.immediate ? SpanKind.CLIENT : SpanKind.PRODUCER,
|
|
9869
|
+
attributes: {
|
|
9870
|
+
"activitypub.activity.type": activityType,
|
|
9871
|
+
"activitypub.activity.to": activity.toIds.map((to) => to.href),
|
|
9872
|
+
"activitypub.activity.cc": activity.ccIds.map((cc) => cc.href),
|
|
9873
|
+
"activitypub.activity.bto": activity.btoIds.map((bto) => bto.href),
|
|
9874
|
+
"activitypub.activity.bcc": activity.bccIds.map((bcc) => bcc.href)
|
|
9875
|
+
}
|
|
9876
|
+
}, async (span) => {
|
|
9877
|
+
observation?.setSpan(span, parentSpanContext);
|
|
9878
|
+
try {
|
|
9879
|
+
const activityId = activity.id?.href;
|
|
9880
|
+
if (activityId != null) span.setAttribute("activitypub.activity.id", activityId);
|
|
9881
|
+
observation?.setActivity({
|
|
9882
|
+
type: activityType,
|
|
9883
|
+
...activityId == null ? {} : { id: activityId }
|
|
9884
|
+
});
|
|
9885
|
+
if (await this.sendActivityInternal(sender, recipients, activity, options, span, observation, provenance)) this.#deliveryState.delivered = true;
|
|
9886
|
+
} catch (e) {
|
|
9887
|
+
span.setStatus({
|
|
9888
|
+
code: SpanStatusCode.ERROR,
|
|
9889
|
+
message: String(e)
|
|
9890
|
+
});
|
|
9891
|
+
throw e;
|
|
9892
|
+
} finally {
|
|
9893
|
+
span.end();
|
|
9894
|
+
}
|
|
9895
|
+
});
|
|
9896
|
+
}, "send", options.dispatchId);
|
|
8650
9897
|
}
|
|
8651
9898
|
forwardActivity(forwarder, recipients, options) {
|
|
8652
9899
|
return forwardActivity(this, "outbox", forwarder, recipients, options).then((delivered) => {
|
|
@@ -8655,6 +9902,7 @@ var OutboxContextImpl = class OutboxContextImpl extends ContextImpl {
|
|
|
8655
9902
|
}
|
|
8656
9903
|
clone(data) {
|
|
8657
9904
|
return new OutboxContextImpl(this.identifier, this.activity, this.activityId, this.activityType, {
|
|
9905
|
+
[outboundProvenance]: getOutboundProvenance(this),
|
|
8658
9906
|
url: this.url,
|
|
8659
9907
|
federation: this.federation,
|
|
8660
9908
|
data,
|
|
@@ -8716,4 +9964,4 @@ function getRequestId(request) {
|
|
|
8716
9964
|
return `req_${Date.now().toString(36)}${Math.random().toString(36).slice(2, 8)}`;
|
|
8717
9965
|
}
|
|
8718
9966
|
//#endregion
|
|
8719
|
-
export { getEmbeddedFirstGateway as C, handleNodeInfoJrd as D, handleNodeInfo as E, actorDehydrator as O, getCompactRootId as S, handleBenchmarkTrigger as T, coversDelivery as _, OutboxContextImpl as a, PORTABLE_OBJECT_CONTENT_TYPE as b, assertPortableActorActivity as c, handleCustomCollection as d, handleInbox as f, respondWithObjectIfAcceptable as g, respondWithObject as h, KvSpecDeterminer as i, autoIdAssigner as k, handleActor as l, handleOutbox as m, FederationImpl as n, createFederation as o, handleObject as p, InboxContextImpl as r, handleWebFinger as s, ContextImpl as t, handleCollection as u, forwardPortableInboxActivity as v, warnCompatibleIdsInJson as w, parseHashlinkGatewayRequest as x, resolvePortableInboxForwardingOptions as y };
|
|
9967
|
+
export { OutboundAttemptObservation as A, getEmbeddedFirstGateway as C, handleNodeInfoJrd as D, handleNodeInfo as E, observeOutboundBroadcast as M, actorDehydrator as O, getCompactRootId as S, handleBenchmarkTrigger as T, coversDelivery as _, OutboxContextImpl as a, PORTABLE_OBJECT_CONTENT_TYPE as b, assertPortableActorActivity as c, handleCustomCollection as d, handleInbox as f, respondWithObjectIfAcceptable as g, respondWithObject as h, KvSpecDeterminer as i, OutboundBroadcastObservation as j, autoIdAssigner as k, handleActor as l, handleOutbox as m, FederationImpl as n, createFederation as o, handleObject as p, InboxContextImpl as r, handleWebFinger as s, ContextImpl as t, handleCollection as u, forwardPortableInboxActivity as v, warnCompatibleIdsInJson as w, parseHashlinkGatewayRequest as x, resolvePortableInboxForwardingOptions as y };
|