@fedify/fedify 2.5.0-dev.2609 → 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-BLCvt0zp.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-DNhLvscb.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-DYkYmKp0.mjs → deno-CRXki9-m.mjs} +1 -1
- package/dist/{docloader-B9RO5nh9.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-DzMdu4zU.cjs → federation-DRl81KbJ.cjs} +2358 -405
- package/dist/{http-EoFzzh1s.js → http-C2H4DT6t.js} +62 -32
- package/dist/{http-BdTJujR0.cjs → http-DHSIMipE.cjs} +62 -32
- package/dist/{http-TgeyuTDM.mjs → http-DqOXkYbX.mjs} +64 -34
- package/dist/{inbox-ChkbRxCN.mjs → inbox-C4iW1g0F.mjs} +90 -3
- package/dist/{inbox-view-BosBZdlt.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-CMgOYiLU.js → kv-cache-B-x_OrBo.js} +1 -1
- package/dist/{kv-cache-B0o18_Ni.cjs → kv-cache-CHR_BVyM.cjs} +1 -1
- package/dist/{kv-cache-CTA2i08a.mjs → kv-cache-DJsxu9nI.mjs} +1 -1
- package/dist/{ld-CZsMAfOw.mjs → ld-C5JOBBFj.mjs} +2 -2
- package/dist/{ld-Inagww4R.d.ts → ld-o4LnhCYY.d.ts} +2 -2
- package/dist/{metrics-Kc9mP1LD.mjs → metrics-CTteRnEr.mjs} +1 -1
- package/dist/{metrics-DoRtI0qU.d.ts → metrics-DVKYY6K1.d.ts} +1 -1
- package/dist/{middleware-B-pjIvQ8.js → middleware-B-tMVSUA.js} +2303 -351
- package/dist/{middleware-CIRoIVSU.mjs → middleware-BkRqMGPU.mjs} +1573 -325
- package/dist/{middleware-C1coPz8l.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-Dwz4fGUW.mjs → owner-CKlRIkeF.mjs} +3 -3
- package/dist/{portable-collection-Oxfavx8f.mjs → portable-collection-B7RBHVGl.mjs} +2 -2
- package/dist/{portable-collection-BikiijSk.js → portable-collection-CByyPjLc.js} +2 -2
- package/dist/{portable-collection-Ci6vIhbR.cjs → portable-collection-CmLjeKsP.cjs} +2 -2
- package/dist/{portable-key--fWpTNDN.mjs → portable-key-Clg0IH8C.mjs} +1 -1
- package/dist/{proof-D6Kb0Qq9.mjs → proof-tAb5_Dq4.mjs} +3 -3
- package/dist/{send-BFti0L3I.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-DlYL_raW.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 +7 -7
- package/skills/fedify/SKILL.md +60 -1
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
const { URLPattern } = require("urlpattern-polyfill");
|
|
2
2
|
const require_rolldown_runtime = require("./rolldown-runtime-Dmm3t92W.cjs");
|
|
3
3
|
const require_transformers = require("./transformers-BalCrSsr.cjs");
|
|
4
|
-
const require_http = require("./http-
|
|
4
|
+
const require_http = require("./http-DHSIMipE.cjs");
|
|
5
5
|
const require_types = require("./types-hKCCjii0.cjs");
|
|
6
|
-
const require_portable_collection = require("./portable-collection-
|
|
7
|
-
const require_kv_cache = require("./kv-cache-
|
|
6
|
+
const require_portable_collection = require("./portable-collection-CmLjeKsP.cjs");
|
|
7
|
+
const require_kv_cache = require("./kv-cache-CHR_BVyM.cjs");
|
|
8
8
|
let _logtape_logtape = require("@logtape/logtape");
|
|
9
|
+
let _fedify_vocab_runtime = require("@fedify/vocab-runtime");
|
|
9
10
|
let _fedify_uri_template = require("@fedify/uri-template");
|
|
10
11
|
let _fedify_vocab = require("@fedify/vocab");
|
|
11
12
|
let _opentelemetry_api = require("@opentelemetry/api");
|
|
12
|
-
let _fedify_vocab_runtime = require("@fedify/vocab-runtime");
|
|
13
13
|
let _fedify_vocab_runtime_internal_portable_dereference = require("@fedify/vocab-runtime/internal/portable-dereference");
|
|
14
14
|
let byte_encodings_hex = require("byte-encodings/hex");
|
|
15
15
|
let _fedify_vocab_runtime_jsonld = require("@fedify/vocab-runtime/jsonld");
|
|
@@ -23,6 +23,500 @@ let _fedify_webfinger = require("@fedify/webfinger");
|
|
|
23
23
|
let _opentelemetry_sdk_metrics = require("@opentelemetry/sdk-metrics");
|
|
24
24
|
let devalue = require("devalue");
|
|
25
25
|
let node_url = require("node:url");
|
|
26
|
+
//#region src/federation/outbound-document.ts
|
|
27
|
+
const asContext = "https://www.w3.org/ns/activitystreams";
|
|
28
|
+
const asNamespace = `${asContext}#`;
|
|
29
|
+
const blind = /* @__PURE__ */ new Set([
|
|
30
|
+
"bto",
|
|
31
|
+
"bcc",
|
|
32
|
+
"as:bto",
|
|
33
|
+
"as:bcc",
|
|
34
|
+
`${asNamespace}bto`,
|
|
35
|
+
`${asNamespace}bcc`
|
|
36
|
+
]);
|
|
37
|
+
const contextCandidates = [
|
|
38
|
+
asContext,
|
|
39
|
+
"https://w3id.org/security/v1",
|
|
40
|
+
"https://w3id.org/identity/v1",
|
|
41
|
+
"https://gotosocial.org/ns",
|
|
42
|
+
"https://w3id.org/security/data-integrity/v1",
|
|
43
|
+
"https://w3id.org/security/data-integrity/v2"
|
|
44
|
+
];
|
|
45
|
+
function safeContext(context) {
|
|
46
|
+
if (Array.isArray(context)) return context.every(safeContext);
|
|
47
|
+
if (context == null || typeof context !== "object") return false;
|
|
48
|
+
for (const [name, definition] of Object.entries(context)) {
|
|
49
|
+
if (name === "@import") return false;
|
|
50
|
+
const mapping = typeof definition === "string" ? definition : definition != null && typeof definition === "object" ? definition["@id"] : void 0;
|
|
51
|
+
if (name === "as" && mapping !== asNamespace) return false;
|
|
52
|
+
if (name !== "as" && typeof mapping === "string" && mapping.startsWith(asNamespace) && (mapping.endsWith("#") || mapping.endsWith("/") || typeof definition === "object" && definition["@prefix"] === true)) return false;
|
|
53
|
+
if (typeof mapping === "string" && blind.has(mapping) && name !== "bto" && name !== "bcc") return false;
|
|
54
|
+
if ((name === "bto" || name === "bcc") && mapping !== `as:${name}` && mapping !== `${asNamespace}${name}`) return false;
|
|
55
|
+
if (name === "@vocab" && typeof mapping === "string" && mapping.startsWith(asNamespace)) return false;
|
|
56
|
+
if (definition != null && typeof definition === "object" && "@context" in definition && !safeContext(definition["@context"])) return false;
|
|
57
|
+
}
|
|
58
|
+
return true;
|
|
59
|
+
}
|
|
60
|
+
const contexts = new Set(contextCandidates.filter((url) => {
|
|
61
|
+
const document = _fedify_vocab_runtime.preloadedContexts[url];
|
|
62
|
+
return document != null && typeof document === "object" && safeContext(document["@context"]);
|
|
63
|
+
}));
|
|
64
|
+
const generatedTerms = {
|
|
65
|
+
toot: "http://joinmastodon.org/ns#",
|
|
66
|
+
misskey: "https://misskey-hub.net/ns#",
|
|
67
|
+
fedibird: "http://fedibird.com/ns#",
|
|
68
|
+
sensitive: "as:sensitive",
|
|
69
|
+
votersCount: {
|
|
70
|
+
"@id": "toot:votersCount",
|
|
71
|
+
"@type": "http://www.w3.org/2001/XMLSchema#nonNegativeInteger"
|
|
72
|
+
},
|
|
73
|
+
Emoji: "toot:Emoji",
|
|
74
|
+
Hashtag: "as:Hashtag",
|
|
75
|
+
ChatMessage: "http://litepub.social/ns#ChatMessage",
|
|
76
|
+
quote: {
|
|
77
|
+
"@id": "https://w3id.org/fep/044f#quote",
|
|
78
|
+
"@type": "@id"
|
|
79
|
+
},
|
|
80
|
+
quoteUrl: "as:quoteUrl",
|
|
81
|
+
_misskey_quote: "misskey:_misskey_quote",
|
|
82
|
+
quoteUri: "fedibird:quoteUri",
|
|
83
|
+
QuoteAuthorization: "https://w3id.org/fep/044f#QuoteAuthorization",
|
|
84
|
+
QuoteRequest: "https://w3id.org/fep/044f#QuoteRequest",
|
|
85
|
+
quoteAuthorization: {
|
|
86
|
+
"@id": "https://w3id.org/fep/044f#quoteAuthorization",
|
|
87
|
+
"@type": "@id"
|
|
88
|
+
},
|
|
89
|
+
emojiReactions: {
|
|
90
|
+
"@id": "fedibird:emojiReactions",
|
|
91
|
+
"@type": "@id"
|
|
92
|
+
}
|
|
93
|
+
};
|
|
94
|
+
function sameDefinition(value, expected) {
|
|
95
|
+
if (value === expected) return true;
|
|
96
|
+
if (value == null || expected == null || typeof value !== "object" || typeof expected !== "object" || Array.isArray(value)) return false;
|
|
97
|
+
const actual = value;
|
|
98
|
+
const reference = expected;
|
|
99
|
+
return Object.keys(actual).length === Object.keys(reference).length && Object.keys(actual).every((name) => Object.hasOwn(reference, name) && sameDefinition(actual[name], reference[name]));
|
|
100
|
+
}
|
|
101
|
+
function generatedContext(value) {
|
|
102
|
+
if (value == null || typeof value !== "object" || Array.isArray(value)) return false;
|
|
103
|
+
return Object.entries(value).every(([name, definition]) => Object.hasOwn(generatedTerms, name) && sameDefinition(definition, generatedTerms[name]));
|
|
104
|
+
}
|
|
105
|
+
const keywords = /* @__PURE__ */ new Set([
|
|
106
|
+
"@id",
|
|
107
|
+
"@type",
|
|
108
|
+
"@value",
|
|
109
|
+
"@language",
|
|
110
|
+
"@direction",
|
|
111
|
+
"@index",
|
|
112
|
+
"@list",
|
|
113
|
+
"@set",
|
|
114
|
+
"@graph",
|
|
115
|
+
"@reverse",
|
|
116
|
+
"@included",
|
|
117
|
+
"@nest"
|
|
118
|
+
]);
|
|
119
|
+
/** Validate the explicit opt-in before replacing observer registration. */
|
|
120
|
+
function snapshotSenderPayloadOption(value) {
|
|
121
|
+
if (value === void 0) return false;
|
|
122
|
+
if (typeof value !== "boolean") throw new TypeError("Outbound senderPayload must be a boolean.");
|
|
123
|
+
return value;
|
|
124
|
+
}
|
|
125
|
+
/** Project existing plain JSON only; no getters, JSON-LD processing or serializers. */
|
|
126
|
+
function projectSenderPayload(input) {
|
|
127
|
+
try {
|
|
128
|
+
let compact = false;
|
|
129
|
+
let redacted = false;
|
|
130
|
+
const seen = /* @__PURE__ */ new WeakSet();
|
|
131
|
+
const copy = (value, depth, contextData = false) => {
|
|
132
|
+
if (depth >= 64) throw new TypeError("Unsafe document depth.");
|
|
133
|
+
if (value === null || typeof value === "string" || typeof value === "boolean") return value;
|
|
134
|
+
if (typeof value === "number" && Number.isFinite(value)) return value;
|
|
135
|
+
if (typeof value !== "object" || value == null || seen.has(value)) throw new TypeError("Not independent JSON data.");
|
|
136
|
+
const array = Array.isArray(value);
|
|
137
|
+
const prototype = Object.getPrototypeOf(value);
|
|
138
|
+
if (array ? prototype !== Array.prototype : prototype !== Object.prototype && prototype !== null) throw new TypeError("Unsupported document object.");
|
|
139
|
+
seen.add(value);
|
|
140
|
+
try {
|
|
141
|
+
const descriptors = Object.getOwnPropertyDescriptors(value);
|
|
142
|
+
if (Object.hasOwn(descriptors, "toJSON")) throw new TypeError("Custom JSON conversion is not projected.");
|
|
143
|
+
const names = Reflect.ownKeys(descriptors).filter((name) => descriptors[name].enumerable);
|
|
144
|
+
if (names.some((name) => typeof name !== "string" || !("value" in descriptors[name]))) throw new TypeError("Not plain JSON properties.");
|
|
145
|
+
let contextValue;
|
|
146
|
+
if (!contextData && !array && descriptors["@context"]?.enumerable) {
|
|
147
|
+
const context = copy(descriptors["@context"].value, depth + 1, true);
|
|
148
|
+
contextValue = context;
|
|
149
|
+
const urls = typeof context === "string" ? [context] : context;
|
|
150
|
+
if (!Array.isArray(urls) || urls.length === 0 || (depth === 0 ? !urls.includes(asContext) : !compact) || !urls.every((url) => typeof url === "string" ? contexts.has(url) : depth === 0 && generatedContext(url))) throw new TypeError("Unsupported document context.");
|
|
151
|
+
compact = true;
|
|
152
|
+
}
|
|
153
|
+
if (array) {
|
|
154
|
+
const length = descriptors.length.value;
|
|
155
|
+
if (names.length !== length || names.some((name, index) => name !== String(index))) throw new TypeError("Not a JSON array.");
|
|
156
|
+
return Object.freeze(names.map((name) => copy(descriptors[name].value, depth + 1, contextData)));
|
|
157
|
+
}
|
|
158
|
+
const result = Object.create(null);
|
|
159
|
+
for (const name of names) {
|
|
160
|
+
if (contextData) {
|
|
161
|
+
result[name] = copy(descriptors[name].value, depth + 1, true);
|
|
162
|
+
continue;
|
|
163
|
+
}
|
|
164
|
+
if (name === "@context") {
|
|
165
|
+
if (contextValue === void 0 || !compact) throw new TypeError("Scoped context not supported.");
|
|
166
|
+
result[name] = contextValue;
|
|
167
|
+
continue;
|
|
168
|
+
}
|
|
169
|
+
if (!compact && !keywords.has(name) && !/^[A-Za-z][A-Za-z0-9+.-]*:/.test(name)) throw new TypeError("Unresolved document property.");
|
|
170
|
+
const next = copy(descriptors[name].value, depth + 1);
|
|
171
|
+
if (blind.has(name)) {
|
|
172
|
+
redacted = true;
|
|
173
|
+
continue;
|
|
174
|
+
}
|
|
175
|
+
result[name] = next;
|
|
176
|
+
}
|
|
177
|
+
return Object.freeze(result);
|
|
178
|
+
} finally {
|
|
179
|
+
seen.delete(value);
|
|
180
|
+
}
|
|
181
|
+
};
|
|
182
|
+
if (input == null || typeof input !== "object") throw new TypeError("Not an outgoing document.");
|
|
183
|
+
const value = copy(input, 0);
|
|
184
|
+
return Object.freeze({
|
|
185
|
+
stage: "sender-payload",
|
|
186
|
+
type: "available",
|
|
187
|
+
representation: "privacy-projection",
|
|
188
|
+
redacted,
|
|
189
|
+
value
|
|
190
|
+
});
|
|
191
|
+
} catch {
|
|
192
|
+
return Object.freeze({
|
|
193
|
+
stage: "sender-payload",
|
|
194
|
+
type: "unavailable",
|
|
195
|
+
reason: "privacy-filter-unavailable"
|
|
196
|
+
});
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
//#endregion
|
|
200
|
+
//#region src/federation/outbound-body.ts
|
|
201
|
+
/** One pull-driven response reader with an absolute lifetime and no extra queue. */
|
|
202
|
+
var ResponseBodyPump = class {
|
|
203
|
+
#reader;
|
|
204
|
+
#expiresAt;
|
|
205
|
+
#deadline;
|
|
206
|
+
#timer;
|
|
207
|
+
#expireDeadline;
|
|
208
|
+
#pending;
|
|
209
|
+
#timedOut = false;
|
|
210
|
+
#closed = false;
|
|
211
|
+
#ended = false;
|
|
212
|
+
constructor(body, timeoutMs) {
|
|
213
|
+
this.#reader = body.getReader();
|
|
214
|
+
this.#expiresAt = performance.now() + timeoutMs;
|
|
215
|
+
this.#deadline = new Promise((resolve) => {
|
|
216
|
+
this.#expireDeadline = () => resolve(void 0);
|
|
217
|
+
});
|
|
218
|
+
this.#timer = setTimeout(() => this.#expire(), timeoutMs);
|
|
219
|
+
}
|
|
220
|
+
/** Undefined means the absolute lifetime expired, independently of EOF. */
|
|
221
|
+
read() {
|
|
222
|
+
if (this.#closed) return Promise.resolve(this.#timedOut ? void 0 : {
|
|
223
|
+
done: true,
|
|
224
|
+
value: void 0
|
|
225
|
+
});
|
|
226
|
+
if (performance.now() >= this.#expiresAt) {
|
|
227
|
+
this.#expire();
|
|
228
|
+
return Promise.resolve(void 0);
|
|
229
|
+
}
|
|
230
|
+
this.#pending ??= Promise.race([this.#reader.read(), this.#deadline]).then((result) => {
|
|
231
|
+
this.#pending = void 0;
|
|
232
|
+
if (this.#timedOut || performance.now() >= this.#expiresAt) {
|
|
233
|
+
this.#expire();
|
|
234
|
+
return;
|
|
235
|
+
}
|
|
236
|
+
if (this.#closed) return {
|
|
237
|
+
done: true,
|
|
238
|
+
value: void 0
|
|
239
|
+
};
|
|
240
|
+
if (result?.done) {
|
|
241
|
+
this.#ended = true;
|
|
242
|
+
this.close();
|
|
243
|
+
}
|
|
244
|
+
return result;
|
|
245
|
+
}, (error) => {
|
|
246
|
+
this.#pending = void 0;
|
|
247
|
+
this.close();
|
|
248
|
+
throw error;
|
|
249
|
+
});
|
|
250
|
+
return this.#pending;
|
|
251
|
+
}
|
|
252
|
+
/** Shorten the remaining lifetime when ownership passes to diagnostic cleanup. */
|
|
253
|
+
limitLifetime(timeoutMs) {
|
|
254
|
+
if (this.#closed) return;
|
|
255
|
+
this.#expiresAt = Math.min(this.#expiresAt, performance.now() + Math.max(0, timeoutMs));
|
|
256
|
+
clearTimeout(this.#timer);
|
|
257
|
+
if (performance.now() >= this.#expiresAt) this.#expire();
|
|
258
|
+
else this.#timer = setTimeout(() => this.#expire(), this.#expiresAt - performance.now());
|
|
259
|
+
}
|
|
260
|
+
/** Stop pulling and release ownership without awaiting arbitrary cancellation. */
|
|
261
|
+
close() {
|
|
262
|
+
if (this.#closed) return;
|
|
263
|
+
this.#closed = true;
|
|
264
|
+
clearTimeout(this.#timer);
|
|
265
|
+
try {
|
|
266
|
+
if (!this.#ended) this.#reader.cancel().catch(() => {});
|
|
267
|
+
} finally {
|
|
268
|
+
this.#reader.releaseLock();
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
#expire() {
|
|
272
|
+
if (this.#timedOut) return;
|
|
273
|
+
this.#timedOut = true;
|
|
274
|
+
this.#expireDeadline();
|
|
275
|
+
this.close();
|
|
276
|
+
}
|
|
277
|
+
};
|
|
278
|
+
//#endregion
|
|
279
|
+
//#region src/federation/outbound-capture.ts
|
|
280
|
+
const diagnosticTimeoutMs = 1e3;
|
|
281
|
+
const diagnosticMaxBytes = 1024;
|
|
282
|
+
const diagnostics = /* @__PURE__ */ new WeakMap();
|
|
283
|
+
/** Reuse a selected capture's diagnostic preview instead of acquiring a second reader. */
|
|
284
|
+
function takeCapturedDiagnostic(response) {
|
|
285
|
+
const value = diagnostics.get(response);
|
|
286
|
+
diagnostics.delete(response);
|
|
287
|
+
return value;
|
|
288
|
+
}
|
|
289
|
+
/** Validate and copy before replacing the current observer registration. */
|
|
290
|
+
function snapshotBodyCapture(value) {
|
|
291
|
+
if (value === void 0) return void 0;
|
|
292
|
+
if (value == null || typeof value !== "object" || typeof value.onBody !== "function") throw new TypeError("Response body capture requires an onBody callback.");
|
|
293
|
+
const { maxBytes, timeoutMs, responses = "all", onBody } = value;
|
|
294
|
+
if (typeof maxBytes !== "number" || typeof timeoutMs !== "number") throw new TypeError("Response body capture requires numeric maxBytes and timeoutMs.");
|
|
295
|
+
if (!Number.isSafeInteger(maxBytes) || maxBytes <= 0) throw new RangeError("Response body capture maxBytes must be a positive safe integer.");
|
|
296
|
+
if (!Number.isFinite(timeoutMs) || timeoutMs <= 0 || Math.ceil(timeoutMs) > 2147483647) throw new RangeError("Response body capture timeoutMs is outside the timer range.");
|
|
297
|
+
if (responses !== "all" && responses !== "non-success-http-responses") throw new TypeError("Invalid response body capture selector.");
|
|
298
|
+
return Object.freeze({
|
|
299
|
+
maxBytes,
|
|
300
|
+
timeoutMs: Math.ceil(timeoutMs),
|
|
301
|
+
responses,
|
|
302
|
+
onBody
|
|
303
|
+
});
|
|
304
|
+
}
|
|
305
|
+
/** One source reader feeds both the revocable application view and bounded diagnostics. */
|
|
306
|
+
async function captureResponseBody(response, options, onBody) {
|
|
307
|
+
if (options.responses === "non-success-http-responses" && response.ok) return Object.freeze({
|
|
308
|
+
type: "filtered",
|
|
309
|
+
bytesExposed: 0
|
|
310
|
+
});
|
|
311
|
+
if (response.body == null) return Object.freeze({
|
|
312
|
+
type: "no-body",
|
|
313
|
+
bytesExposed: 0
|
|
314
|
+
});
|
|
315
|
+
const start = performance.now();
|
|
316
|
+
const expires = start + options.timeoutMs;
|
|
317
|
+
let pump;
|
|
318
|
+
try {
|
|
319
|
+
pump = new ResponseBodyPump(response.body, Math.max(options.timeoutMs, response.ok ? 0 : diagnosticTimeoutMs));
|
|
320
|
+
} catch (error) {
|
|
321
|
+
return Object.freeze({
|
|
322
|
+
type: "failed",
|
|
323
|
+
phase: "body",
|
|
324
|
+
error,
|
|
325
|
+
bytesExposed: 0
|
|
326
|
+
});
|
|
327
|
+
}
|
|
328
|
+
const signal = new AbortController();
|
|
329
|
+
let controller;
|
|
330
|
+
let active = true;
|
|
331
|
+
let bytesExposed = 0;
|
|
332
|
+
let result;
|
|
333
|
+
let eof = false;
|
|
334
|
+
let diagnosticBytes = 0;
|
|
335
|
+
let diagnosticTruncated = false;
|
|
336
|
+
let diagnosticFailed = false;
|
|
337
|
+
const preview = [];
|
|
338
|
+
const decoder = new TextDecoder();
|
|
339
|
+
let pending;
|
|
340
|
+
const read = () => pending ??= pump.read().then((value) => {
|
|
341
|
+
pending = void 0;
|
|
342
|
+
if (value?.done) eof = true;
|
|
343
|
+
if (!response.ok && value != null && !value.done && performance.now() - start < diagnosticTimeoutMs) {
|
|
344
|
+
const remaining = diagnosticMaxBytes - diagnosticBytes;
|
|
345
|
+
const chunk = value.value;
|
|
346
|
+
if (chunk.length > remaining) diagnosticTruncated = true;
|
|
347
|
+
if (remaining > 0 && chunk.length > 0) {
|
|
348
|
+
const selected = chunk.subarray(0, remaining);
|
|
349
|
+
preview.push(decoder.decode(selected, { stream: true }));
|
|
350
|
+
diagnosticBytes += selected.length;
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
return value;
|
|
354
|
+
}, (error) => {
|
|
355
|
+
pending = void 0;
|
|
356
|
+
diagnosticFailed = true;
|
|
357
|
+
throw error;
|
|
358
|
+
});
|
|
359
|
+
const revoke = (reason) => {
|
|
360
|
+
active = false;
|
|
361
|
+
if (!signal.signal.aborted) signal.abort(reason);
|
|
362
|
+
try {
|
|
363
|
+
controller.error(reason);
|
|
364
|
+
} catch {}
|
|
365
|
+
};
|
|
366
|
+
const body = new ReadableStream({
|
|
367
|
+
start(value) {
|
|
368
|
+
controller = value;
|
|
369
|
+
},
|
|
370
|
+
async pull() {
|
|
371
|
+
try {
|
|
372
|
+
while (active) {
|
|
373
|
+
if (performance.now() >= expires) {
|
|
374
|
+
result = { type: "deadline" };
|
|
375
|
+
revoke(new DOMException("Response body capture deadline exceeded.", "TimeoutError"));
|
|
376
|
+
return;
|
|
377
|
+
}
|
|
378
|
+
const value = await read();
|
|
379
|
+
if (!active) return;
|
|
380
|
+
if (performance.now() >= expires || value == null) {
|
|
381
|
+
result = { type: "deadline" };
|
|
382
|
+
revoke(new DOMException("Response body capture deadline exceeded.", "TimeoutError"));
|
|
383
|
+
return;
|
|
384
|
+
}
|
|
385
|
+
if (value.done) {
|
|
386
|
+
result = { type: "complete" };
|
|
387
|
+
active = false;
|
|
388
|
+
controller.close();
|
|
389
|
+
return;
|
|
390
|
+
}
|
|
391
|
+
if (value.value.length === 0) continue;
|
|
392
|
+
const chunk = value.value.slice(0, options.maxBytes - bytesExposed);
|
|
393
|
+
bytesExposed += chunk.length;
|
|
394
|
+
controller.enqueue(chunk);
|
|
395
|
+
if (bytesExposed === options.maxBytes) {
|
|
396
|
+
result = { type: "limit-reached" };
|
|
397
|
+
revoke(new DOMException("Response body capture byte limit reached.", "QuotaExceededError"));
|
|
398
|
+
}
|
|
399
|
+
return;
|
|
400
|
+
}
|
|
401
|
+
} catch (error) {
|
|
402
|
+
if (active) {
|
|
403
|
+
result = {
|
|
404
|
+
type: "failed",
|
|
405
|
+
phase: "body",
|
|
406
|
+
error
|
|
407
|
+
};
|
|
408
|
+
revoke(error);
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
},
|
|
412
|
+
cancel(reason) {
|
|
413
|
+
result = {
|
|
414
|
+
type: "consumer-stopped",
|
|
415
|
+
reason
|
|
416
|
+
};
|
|
417
|
+
revoke(reason);
|
|
418
|
+
}
|
|
419
|
+
}, { highWaterMark: 0 });
|
|
420
|
+
let timer;
|
|
421
|
+
const deadline = new Promise((resolve) => {
|
|
422
|
+
timer = setTimeout(() => {
|
|
423
|
+
result = { type: "deadline" };
|
|
424
|
+
revoke(new DOMException("Response body capture deadline exceeded.", "TimeoutError"));
|
|
425
|
+
resolve();
|
|
426
|
+
}, Math.max(0, expires - performance.now()));
|
|
427
|
+
});
|
|
428
|
+
const callback = Promise.resolve().then(() => onBody(body, signal.signal)).then(() => {}, (error) => {
|
|
429
|
+
if (result == null || result.type === "complete") result = {
|
|
430
|
+
type: "failed",
|
|
431
|
+
phase: "observer",
|
|
432
|
+
error
|
|
433
|
+
};
|
|
434
|
+
});
|
|
435
|
+
try {
|
|
436
|
+
await Promise.race([callback, deadline]);
|
|
437
|
+
clearTimeout(timer);
|
|
438
|
+
if (performance.now() >= expires) result = { type: "deadline" };
|
|
439
|
+
result ??= { type: "callback-returned" };
|
|
440
|
+
revoke(new DOMException("Response body capture finished.", "AbortError"));
|
|
441
|
+
if (!response.ok) {
|
|
442
|
+
pump.limitLifetime(diagnosticTimeoutMs - (performance.now() - start));
|
|
443
|
+
try {
|
|
444
|
+
while (!eof && !diagnosticTruncated && performance.now() - start < diagnosticTimeoutMs) if (await read() == null) break;
|
|
445
|
+
} catch {}
|
|
446
|
+
diagnostics.set(response, preview.join("") + (diagnosticTruncated ? "… (truncated)" : !eof && !diagnosticFailed ? "… (timed out)" : ""));
|
|
447
|
+
}
|
|
448
|
+
return Object.freeze({
|
|
449
|
+
...result,
|
|
450
|
+
bytesExposed
|
|
451
|
+
});
|
|
452
|
+
} finally {
|
|
453
|
+
clearTimeout(timer);
|
|
454
|
+
pump.close();
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
//#endregion
|
|
458
|
+
//#region src/federation/outbound-headers.ts
|
|
459
|
+
const protectedHeaders = /* @__PURE__ */ new Set([
|
|
460
|
+
"authorization",
|
|
461
|
+
"proxy-authorization",
|
|
462
|
+
"cookie",
|
|
463
|
+
"set-cookie",
|
|
464
|
+
"signature",
|
|
465
|
+
"signature-input",
|
|
466
|
+
"authentication-info",
|
|
467
|
+
"proxy-authentication-info",
|
|
468
|
+
"x-api-key",
|
|
469
|
+
"x-auth-token"
|
|
470
|
+
]);
|
|
471
|
+
function snapshotNames(names) {
|
|
472
|
+
if (!Array.isArray(names)) throw new TypeError("Outbound header names must be an array.");
|
|
473
|
+
const normalized = /* @__PURE__ */ new Set();
|
|
474
|
+
for (const name of names) {
|
|
475
|
+
if (typeof name !== "string" || !/^[!#$%&'*+.^_`|~0-9A-Za-z-]+$/.test(name) || name.includes("*")) throw new TypeError("Outbound header names must be literal HTTP tokens.");
|
|
476
|
+
normalized.add(name.toLowerCase());
|
|
477
|
+
}
|
|
478
|
+
return Object.freeze([...normalized].sort());
|
|
479
|
+
}
|
|
480
|
+
/** Validate/copy policy before registration replaces the selected observers. */
|
|
481
|
+
function snapshotHeaderPolicy(policy) {
|
|
482
|
+
if (policy === void 0) return void 0;
|
|
483
|
+
if (policy == null || typeof policy !== "object") throw new TypeError("Outbound header policy must be an object.");
|
|
484
|
+
const { allow, redact, maxBytes } = policy;
|
|
485
|
+
if (typeof maxBytes !== "number") throw new TypeError("Outbound header maxBytes must be a number.");
|
|
486
|
+
if (!Number.isSafeInteger(maxBytes) || maxBytes <= 0) throw new RangeError("Outbound header maxBytes must be a positive safe integer.");
|
|
487
|
+
return Object.freeze({
|
|
488
|
+
allow: snapshotNames(allow),
|
|
489
|
+
...redact === void 0 ? {} : { redact: snapshotNames(redact) },
|
|
490
|
+
maxBytes
|
|
491
|
+
});
|
|
492
|
+
}
|
|
493
|
+
/** Copy only selected/redacted entries; budgets never truncate a header value. */
|
|
494
|
+
function snapshotHeaders(headers, policy) {
|
|
495
|
+
const redact = /* @__PURE__ */ new Set([...protectedHeaders, ...policy.redact ?? []]);
|
|
496
|
+
const entries = [];
|
|
497
|
+
const encoder = new TextEncoder();
|
|
498
|
+
let bytes = 0;
|
|
499
|
+
let limitReached = false;
|
|
500
|
+
for (const name of policy.allow) {
|
|
501
|
+
if (!headers.has(name)) continue;
|
|
502
|
+
for (const [key, rawValue] of headers) {
|
|
503
|
+
if (key !== name) continue;
|
|
504
|
+
const value = redact.has(name) ? "[redacted]" : rawValue;
|
|
505
|
+
const size = encoder.encode(`${name}: ${value}\r\n`).byteLength;
|
|
506
|
+
if (size > policy.maxBytes - bytes) {
|
|
507
|
+
limitReached = true;
|
|
508
|
+
continue;
|
|
509
|
+
}
|
|
510
|
+
entries.push(Object.freeze([name, value]));
|
|
511
|
+
bytes += size;
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
return Object.freeze({
|
|
515
|
+
entries: Object.freeze(entries),
|
|
516
|
+
limitReached
|
|
517
|
+
});
|
|
518
|
+
}
|
|
519
|
+
//#endregion
|
|
26
520
|
//#region src/federation/activity-listener.ts
|
|
27
521
|
var ActivityListenerSet = class {
|
|
28
522
|
#listeners;
|
|
@@ -108,6 +602,7 @@ var FederationBuilderImpl = class {
|
|
|
108
602
|
inboxListeners;
|
|
109
603
|
outboxListeners;
|
|
110
604
|
inboxRequestFinishedHandler;
|
|
605
|
+
outboundObservers = globalThis.Object.freeze({});
|
|
111
606
|
inboxErrorHandler;
|
|
112
607
|
outboxListenerErrorHandler;
|
|
113
608
|
outboxAuthorizePredicate;
|
|
@@ -170,6 +665,7 @@ var FederationBuilderImpl = class {
|
|
|
170
665
|
f.inboxListeners = this.inboxListeners?.clone();
|
|
171
666
|
f.outboxListeners = this.outboxListeners?.clone();
|
|
172
667
|
f.inboxRequestFinishedHandler = this.inboxRequestFinishedHandler;
|
|
668
|
+
f.setOutboundObservers(this.outboundObservers);
|
|
173
669
|
f.inboxErrorHandler = this.inboxErrorHandler;
|
|
174
670
|
f.outboxListenerErrorHandler = this.outboxListenerErrorHandler;
|
|
175
671
|
f.outboxAuthorizePredicate = this.outboxAuthorizePredicate;
|
|
@@ -317,6 +813,19 @@ var FederationBuilderImpl = class {
|
|
|
317
813
|
};
|
|
318
814
|
return setters;
|
|
319
815
|
}
|
|
816
|
+
setOutboundObservers(observers) {
|
|
817
|
+
this.outboundObservers = globalThis.Object.freeze({
|
|
818
|
+
senderPayload: snapshotSenderPayloadOption(observers.senderPayload),
|
|
819
|
+
responseBodyCapture: snapshotBodyCapture(observers.responseBodyCapture),
|
|
820
|
+
requestHeaders: snapshotHeaderPolicy(observers.requestHeaders),
|
|
821
|
+
responseHeaders: snapshotHeaderPolicy(observers.responseHeaders),
|
|
822
|
+
onBroadcast: observers.onBroadcast,
|
|
823
|
+
onDelivery: observers.onDelivery,
|
|
824
|
+
onAttempt: observers.onAttempt,
|
|
825
|
+
onHttpExchange: observers.onHttpExchange,
|
|
826
|
+
onWorker: observers.onWorker
|
|
827
|
+
});
|
|
828
|
+
}
|
|
320
829
|
setNodeInfoDispatcher(path, dispatcher) {
|
|
321
830
|
if (this.router.has("nodeInfo")) throw new _fedify_uri_template.RouterError("NodeInfo dispatcher already set.");
|
|
322
831
|
if (_fedify_uri_template.Router.variables(path).size !== 0) throw new _fedify_uri_template.RouterError("Path for NodeInfo dispatcher must have no variables.");
|
|
@@ -882,13 +1391,13 @@ function warnCompatibleActorId(actorId, gateway) {
|
|
|
882
1391
|
}
|
|
883
1392
|
//#endregion
|
|
884
1393
|
//#region src/federation/circuit-breaker.ts
|
|
885
|
-
const Temporal$
|
|
1394
|
+
const Temporal$12 = require_http.Temporal;
|
|
886
1395
|
const LEGACY_SWEEP_MARKER = ["__fedify_meta", "circuit_breaker_state_ttl_sweep_v2"];
|
|
887
1396
|
const LEGACY_SWEEP_DELETING_MARKER = { __fedifyDeletingCircuitBreakerLegacyState: true };
|
|
888
1397
|
const CIRCUIT_BREAKER_STATE_VERSION = 1;
|
|
889
|
-
const LEGACY_SWEEP_LOCK_TTL = Temporal$
|
|
890
|
-
const LEGACY_SWEEP_RENEW_INTERVAL = Temporal$
|
|
891
|
-
const LEGACY_SWEEP_RETRY_WINDOW = Temporal$
|
|
1398
|
+
const LEGACY_SWEEP_LOCK_TTL = Temporal$12.Duration.from({ minutes: 5 });
|
|
1399
|
+
const LEGACY_SWEEP_RENEW_INTERVAL = Temporal$12.Duration.from({ minutes: 1 });
|
|
1400
|
+
const LEGACY_SWEEP_RETRY_WINDOW = Temporal$12.Duration.from({ hours: 168 });
|
|
892
1401
|
const LEGACY_SWEEP_WAIT_INTERVAL = 10;
|
|
893
1402
|
const LEGACY_SWEEP_CAS_ATTEMPTS = 3;
|
|
894
1403
|
/**
|
|
@@ -907,7 +1416,7 @@ var CircuitBreaker = class {
|
|
|
907
1416
|
this.#kv = options.kv;
|
|
908
1417
|
this.#prefix = options.prefix;
|
|
909
1418
|
this.#options = normalizeCircuitBreakerOptions(options.options ?? {});
|
|
910
|
-
this.#now = options.now ?? (() => Temporal$
|
|
1419
|
+
this.#now = options.now ?? (() => Temporal$12.Now.instant());
|
|
911
1420
|
this.#stateChangeObserver = options.stateChangeObserver;
|
|
912
1421
|
}
|
|
913
1422
|
get options() {
|
|
@@ -921,7 +1430,7 @@ var CircuitBreaker = class {
|
|
|
921
1430
|
this.#sweepLegacyStates();
|
|
922
1431
|
const heldSince = parseHeldSince(message.circuitHeldSince);
|
|
923
1432
|
const now = this.#now();
|
|
924
|
-
if (heldSince != null && Temporal$
|
|
1433
|
+
if (heldSince != null && Temporal$12.Instant.compare(heldSince.add(this.#options.heldActivityTtl), now) <= 0) return {
|
|
925
1434
|
type: "drop",
|
|
926
1435
|
heldSince
|
|
927
1436
|
};
|
|
@@ -933,12 +1442,12 @@ var CircuitBreaker = class {
|
|
|
933
1442
|
probe: false
|
|
934
1443
|
};
|
|
935
1444
|
if (oldState.state === "half-open") {
|
|
936
|
-
const halfOpened = oldState.halfOpened == null ? void 0 : Temporal$
|
|
1445
|
+
const halfOpened = oldState.halfOpened == null ? void 0 : Temporal$12.Instant.from(oldState.halfOpened);
|
|
937
1446
|
if (halfOpened != null) {
|
|
938
1447
|
const staleAt = halfOpened.add(this.#options.recoveryDelay);
|
|
939
|
-
if (Temporal$
|
|
1448
|
+
if (Temporal$12.Instant.compare(now, staleAt) < 0) {
|
|
940
1449
|
const releaseAt = now.add(this.#options.releaseInterval);
|
|
941
|
-
const retryAt = Temporal$
|
|
1450
|
+
const retryAt = Temporal$12.Instant.compare(releaseAt, staleAt) < 0 ? releaseAt : staleAt;
|
|
942
1451
|
const cappedRetryAt = this.#capHeldRetryAt(now, heldSince, retryAt);
|
|
943
1452
|
return {
|
|
944
1453
|
type: "hold",
|
|
@@ -960,8 +1469,8 @@ var CircuitBreaker = class {
|
|
|
960
1469
|
lastConflictingState = "half-open";
|
|
961
1470
|
continue;
|
|
962
1471
|
}
|
|
963
|
-
const probeAt = (oldState.opened == null ? now : Temporal$
|
|
964
|
-
if (Temporal$
|
|
1472
|
+
const probeAt = (oldState.opened == null ? now : Temporal$12.Instant.from(oldState.opened)).add(this.#options.recoveryDelay);
|
|
1473
|
+
if (Temporal$12.Instant.compare(now, probeAt) < 0) {
|
|
965
1474
|
const retryAt = this.#capHeldRetryAt(now, heldSince, probeAt);
|
|
966
1475
|
return {
|
|
967
1476
|
type: "hold",
|
|
@@ -1026,7 +1535,7 @@ var CircuitBreaker = class {
|
|
|
1026
1535
|
for (let attempt = 0; attempt < 10; attempt++) {
|
|
1027
1536
|
const { state: oldState, raw: oldValue } = await this.#get(remoteHost);
|
|
1028
1537
|
if (oldState?.state === "open") return void 0;
|
|
1029
|
-
const oldFailures = oldState?.failures.map(Temporal$
|
|
1538
|
+
const oldFailures = oldState?.failures.map(Temporal$12.Instant.from) ?? [];
|
|
1030
1539
|
const failures = this.#options.pruneFailures([...oldFailures, now], now);
|
|
1031
1540
|
let newState;
|
|
1032
1541
|
let transition;
|
|
@@ -1110,7 +1619,7 @@ var CircuitBreaker = class {
|
|
|
1110
1619
|
for await (const { key, value } of this.#kv.list(this.#prefix)) {
|
|
1111
1620
|
if (key.length !== this.#prefix.length + 1) continue;
|
|
1112
1621
|
await this.#migrateLegacyState(key, value);
|
|
1113
|
-
if (Temporal$
|
|
1622
|
+
if (Temporal$12.Instant.compare(this.#now(), leaseRenewedAt.add(LEGACY_SWEEP_RENEW_INTERVAL)) >= 0) {
|
|
1114
1623
|
const renewed = await this.#renewLegacySweep(markerKey, marker);
|
|
1115
1624
|
if (renewed == null) return;
|
|
1116
1625
|
marker = renewed;
|
|
@@ -1134,7 +1643,7 @@ var CircuitBreaker = class {
|
|
|
1134
1643
|
}
|
|
1135
1644
|
#finishLegacySweepMarker(marker) {
|
|
1136
1645
|
const retryUntil = getLegacySweepRetryUntil(marker);
|
|
1137
|
-
if (retryUntil != null && Temporal$
|
|
1646
|
+
if (retryUntil != null && Temporal$12.Instant.compare(this.#now(), Temporal$12.Instant.from(retryUntil)) < 0) return {
|
|
1138
1647
|
state: "done",
|
|
1139
1648
|
retryUntil
|
|
1140
1649
|
};
|
|
@@ -1177,7 +1686,7 @@ var CircuitBreaker = class {
|
|
|
1177
1686
|
#isLegacySweepDone() {
|
|
1178
1687
|
if (this.#legacySweepDone === "final") return true;
|
|
1179
1688
|
if (this.#legacySweepDone == null) return false;
|
|
1180
|
-
if (Temporal$
|
|
1689
|
+
if (Temporal$12.Instant.compare(this.#now(), this.#legacySweepDone) < 0) return true;
|
|
1181
1690
|
this.#legacySweepDone = void 0;
|
|
1182
1691
|
return false;
|
|
1183
1692
|
}
|
|
@@ -1187,7 +1696,7 @@ var CircuitBreaker = class {
|
|
|
1187
1696
|
}
|
|
1188
1697
|
#capHeldRetryAt(now, heldSince, retryAt) {
|
|
1189
1698
|
const expiresAt = (heldSince ?? now).add(this.#options.heldActivityTtl);
|
|
1190
|
-
return Temporal$
|
|
1699
|
+
return Temporal$12.Instant.compare(expiresAt, retryAt) < 0 ? expiresAt : retryAt;
|
|
1191
1700
|
}
|
|
1192
1701
|
async #get(remoteHost) {
|
|
1193
1702
|
const value = await this.#kv.get(this.#key(remoteHost));
|
|
@@ -1262,7 +1771,7 @@ function normalizeCircuitBreakerOptions(options) {
|
|
|
1262
1771
|
assertPositiveDuration(releaseInterval, "releaseInterval");
|
|
1263
1772
|
if (configuredStateTtl != null) {
|
|
1264
1773
|
assertPositiveDuration(configuredStateTtl, "stateTtl");
|
|
1265
|
-
if (Temporal$
|
|
1774
|
+
if (Temporal$12.Duration.compare(configuredStateTtl, recoveryDelay) < 0) throw new RangeError("stateTtl must be at least recoveryDelay.");
|
|
1266
1775
|
}
|
|
1267
1776
|
let failure;
|
|
1268
1777
|
let pruneFailures;
|
|
@@ -1274,13 +1783,13 @@ function normalizeCircuitBreakerOptions(options) {
|
|
|
1274
1783
|
assertPositiveDuration(failureWindow, "failureWindow");
|
|
1275
1784
|
pruneFailures = (timestamps, now) => {
|
|
1276
1785
|
const earliest = now.subtract(failureWindow);
|
|
1277
|
-
return timestamps.filter((timestamp) => Temporal$
|
|
1786
|
+
return timestamps.filter((timestamp) => Temporal$12.Instant.compare(timestamp, earliest) >= 0).slice(-failureThreshold);
|
|
1278
1787
|
};
|
|
1279
1788
|
failure = (timestamps) => {
|
|
1280
1789
|
if (timestamps.length < failureThreshold) return false;
|
|
1281
1790
|
const first = timestamps[timestamps.length - failureThreshold];
|
|
1282
1791
|
const last = timestamps[timestamps.length - 1];
|
|
1283
|
-
return Temporal$
|
|
1792
|
+
return Temporal$12.Duration.compare(first.until(last), failureWindow) <= 0;
|
|
1284
1793
|
};
|
|
1285
1794
|
stateTtl = configuredStateTtl ?? maxDuration(recoveryDelay.add(failureWindow), recoveryDelay.add(heldActivityTtl));
|
|
1286
1795
|
} else {
|
|
@@ -1300,7 +1809,7 @@ function normalizeCircuitBreakerOptions(options) {
|
|
|
1300
1809
|
};
|
|
1301
1810
|
}
|
|
1302
1811
|
function maxDuration(duration, ...durations) {
|
|
1303
|
-
return durations.reduce((max, candidate) => Temporal$
|
|
1812
|
+
return durations.reduce((max, candidate) => Temporal$12.Duration.compare(candidate, max) > 0 ? candidate : max, duration);
|
|
1304
1813
|
}
|
|
1305
1814
|
function isLegacySweepDone(value, now) {
|
|
1306
1815
|
return getLegacySweepDoneUntil(value, now) != null;
|
|
@@ -1309,8 +1818,8 @@ function getLegacySweepDoneUntil(value, now) {
|
|
|
1309
1818
|
if (typeof value !== "object" || value == null || !("state" in value)) return;
|
|
1310
1819
|
if (value.state === "final") return "final";
|
|
1311
1820
|
if (value.state === "done" && "retryUntil" in value && typeof value.retryUntil === "string") try {
|
|
1312
|
-
const retryUntil = Temporal$
|
|
1313
|
-
return Temporal$
|
|
1821
|
+
const retryUntil = Temporal$12.Instant.from(value.retryUntil);
|
|
1822
|
+
return Temporal$12.Instant.compare(now, retryUntil) < 0 ? retryUntil : void 0;
|
|
1314
1823
|
} catch {
|
|
1315
1824
|
return;
|
|
1316
1825
|
}
|
|
@@ -1318,7 +1827,7 @@ function getLegacySweepDoneUntil(value, now) {
|
|
|
1318
1827
|
function isLegacySweepRetryDue(value, now) {
|
|
1319
1828
|
if (typeof value !== "object" || value == null || !("state" in value) || value.state !== "done" || !("retryUntil" in value) || typeof value.retryUntil !== "string") return false;
|
|
1320
1829
|
try {
|
|
1321
|
-
return Temporal$
|
|
1830
|
+
return Temporal$12.Instant.compare(now, Temporal$12.Instant.from(value.retryUntil)) >= 0;
|
|
1322
1831
|
} catch {
|
|
1323
1832
|
return false;
|
|
1324
1833
|
}
|
|
@@ -1329,7 +1838,7 @@ function isLegacySweepActive(value, now) {
|
|
|
1329
1838
|
function isLegacySweepInProgress(value, now) {
|
|
1330
1839
|
if (typeof value !== "object" || value == null || !("state" in value) || value.state !== "sweeping" || !("started" in value) || typeof value.started !== "string") return false;
|
|
1331
1840
|
try {
|
|
1332
|
-
return Temporal$
|
|
1841
|
+
return Temporal$12.Instant.compare(now, Temporal$12.Instant.from(value.started).add(LEGACY_SWEEP_LOCK_TTL)) < 0;
|
|
1333
1842
|
} catch {
|
|
1334
1843
|
return false;
|
|
1335
1844
|
}
|
|
@@ -1337,7 +1846,7 @@ function isLegacySweepInProgress(value, now) {
|
|
|
1337
1846
|
function getLegacySweepRetryUntil(value) {
|
|
1338
1847
|
if (typeof value !== "object" || value == null || !("retryUntil" in value) || typeof value.retryUntil !== "string") return;
|
|
1339
1848
|
try {
|
|
1340
|
-
Temporal$
|
|
1849
|
+
Temporal$12.Instant.from(value.retryUntil);
|
|
1341
1850
|
return value.retryUntil;
|
|
1342
1851
|
} catch {
|
|
1343
1852
|
return;
|
|
@@ -1361,19 +1870,19 @@ function delay(milliseconds) {
|
|
|
1361
1870
|
return new Promise((resolve) => setTimeout(resolve, milliseconds));
|
|
1362
1871
|
}
|
|
1363
1872
|
function toInstantDuration(duration) {
|
|
1364
|
-
const parsed = Temporal$
|
|
1365
|
-
return Temporal$
|
|
1873
|
+
const parsed = Temporal$12.Duration.from(duration);
|
|
1874
|
+
return Temporal$12.Duration.from({ milliseconds: Math.trunc(parsed.total({
|
|
1366
1875
|
unit: "millisecond",
|
|
1367
|
-
relativeTo: Temporal$
|
|
1876
|
+
relativeTo: Temporal$12.PlainDateTime.from("2026-01-01T00:00:00")
|
|
1368
1877
|
})) });
|
|
1369
1878
|
}
|
|
1370
1879
|
function assertPositiveDuration(duration, name) {
|
|
1371
|
-
if (Temporal$
|
|
1880
|
+
if (Temporal$12.Duration.compare(duration, { seconds: 0 }) <= 0) throw new RangeError(`${name} must be a positive duration.`);
|
|
1372
1881
|
}
|
|
1373
1882
|
function parseHeldSince(value) {
|
|
1374
1883
|
if (value == null) return void 0;
|
|
1375
1884
|
try {
|
|
1376
|
-
return Temporal$
|
|
1885
|
+
return Temporal$12.Instant.from(value);
|
|
1377
1886
|
} catch (error) {
|
|
1378
1887
|
(0, _logtape_logtape.getLogger)([
|
|
1379
1888
|
"fedify",
|
|
@@ -1400,7 +1909,7 @@ function parseStoredCircuitBreakerKvState(value) {
|
|
|
1400
1909
|
const isInstantString = (v) => {
|
|
1401
1910
|
if (typeof v !== "string") return false;
|
|
1402
1911
|
try {
|
|
1403
|
-
Temporal$
|
|
1912
|
+
Temporal$12.Instant.from(v);
|
|
1404
1913
|
return true;
|
|
1405
1914
|
} catch {
|
|
1406
1915
|
return false;
|
|
@@ -1464,6 +1973,7 @@ async function buildCollectionSynchronizationHeader(collectionId, actorIds) {
|
|
|
1464
1973
|
/** Internal state owned by the ingress boundary, never serialized into queues. */
|
|
1465
1974
|
var InboxObservation = class {
|
|
1466
1975
|
inbox;
|
|
1976
|
+
source;
|
|
1467
1977
|
payload = { status: "unavailable" };
|
|
1468
1978
|
activity = null;
|
|
1469
1979
|
authentication = { status: "notDetermined" };
|
|
@@ -1486,8 +1996,9 @@ var InboxObservation = class {
|
|
|
1486
1996
|
}
|
|
1487
1997
|
}
|
|
1488
1998
|
};
|
|
1489
|
-
constructor(inbox) {
|
|
1999
|
+
constructor(inbox, source = void 0) {
|
|
1490
2000
|
this.inbox = inbox;
|
|
2001
|
+
this.source = source;
|
|
1491
2002
|
}
|
|
1492
2003
|
/** Run an ingress operation and project telemetry; fetch owns completion. */
|
|
1493
2004
|
async run(operation, span) {
|
|
@@ -1543,7 +2054,18 @@ var InboxObservation = class {
|
|
|
1543
2054
|
}
|
|
1544
2055
|
async finish(context, handler, outcome) {
|
|
1545
2056
|
if (handler == null) return;
|
|
2057
|
+
const signal = context.request.signal;
|
|
2058
|
+
const requestCancellation = signal.aborted ? Object.freeze({
|
|
2059
|
+
type: "aborted",
|
|
2060
|
+
reason: signal.reason
|
|
2061
|
+
}) : Object.freeze({ type: "not-aborted" });
|
|
1546
2062
|
const report = {
|
|
2063
|
+
...this.source == null ? {} : {
|
|
2064
|
+
inboxRequestId: this.source.inboxRequestId,
|
|
2065
|
+
requestId: this.source.requestId,
|
|
2066
|
+
local: Object.freeze({ ...this.source.context })
|
|
2067
|
+
},
|
|
2068
|
+
requestCancellation,
|
|
1547
2069
|
inbox: this.inbox,
|
|
1548
2070
|
payload: this.payload,
|
|
1549
2071
|
activity: this.activity,
|
|
@@ -1552,7 +2074,7 @@ var InboxObservation = class {
|
|
|
1552
2074
|
outcome
|
|
1553
2075
|
};
|
|
1554
2076
|
try {
|
|
1555
|
-
await handler(context, report);
|
|
2077
|
+
await (0, _logtape_logtape.withContext)(this.source == null ? {} : { inboxRequestId: this.source.inboxRequestId }, () => handler(context, report));
|
|
1556
2078
|
} catch (error) {
|
|
1557
2079
|
try {
|
|
1558
2080
|
(0, _logtape_logtape.getLogger)([
|
|
@@ -2814,6 +3336,92 @@ async function isInAudience(object, actor, options = {}) {
|
|
|
2814
3336
|
return false;
|
|
2815
3337
|
}
|
|
2816
3338
|
//#endregion
|
|
3339
|
+
//#region src/federation/outbound-provenance.ts
|
|
3340
|
+
const outboundProvenance = Symbol("outboundProvenance");
|
|
3341
|
+
const emptyProvenance = Object.freeze({});
|
|
3342
|
+
function getOutboundProvenance(context) {
|
|
3343
|
+
return context[outboundProvenance] ?? emptyProvenance;
|
|
3344
|
+
}
|
|
3345
|
+
function snapshotLocalContext(context) {
|
|
3346
|
+
return Object.freeze({
|
|
3347
|
+
origin: context.origin,
|
|
3348
|
+
host: context.host,
|
|
3349
|
+
hostname: context.hostname,
|
|
3350
|
+
canonicalOrigin: context.canonicalOrigin
|
|
3351
|
+
});
|
|
3352
|
+
}
|
|
3353
|
+
function createInboxRequestSource(request, requestId, canonicalOrigin) {
|
|
3354
|
+
const url = new URL(request.url);
|
|
3355
|
+
return Object.freeze({
|
|
3356
|
+
inboxRequestId: crypto.randomUUID(),
|
|
3357
|
+
requestId,
|
|
3358
|
+
context: snapshotLocalContext({
|
|
3359
|
+
origin: url.origin,
|
|
3360
|
+
host: url.host,
|
|
3361
|
+
hostname: url.hostname,
|
|
3362
|
+
canonicalOrigin: canonicalOrigin ?? url.origin
|
|
3363
|
+
})
|
|
3364
|
+
});
|
|
3365
|
+
}
|
|
3366
|
+
/** The same exact-string budget applies to public input and queued metadata. */
|
|
3367
|
+
function validateDispatchId(value) {
|
|
3368
|
+
if (value === void 0) return;
|
|
3369
|
+
if (typeof value !== "string") throw new TypeError("dispatchId must be a string.");
|
|
3370
|
+
if (value.length > 1024 || new TextEncoder().encode(value).byteLength > 1024) throw new RangeError("dispatchId must not exceed 1,024 UTF-8 bytes.");
|
|
3371
|
+
}
|
|
3372
|
+
/** A new public invocation keeps its HTTP source, with its own dispatch ID. */
|
|
3373
|
+
function invocationProvenance(context, dispatchId) {
|
|
3374
|
+
validateDispatchId(dispatchId);
|
|
3375
|
+
const { dispatchId: _previous, ...source } = getOutboundProvenance(context);
|
|
3376
|
+
return Object.freeze({
|
|
3377
|
+
...source,
|
|
3378
|
+
...dispatchId === void 0 ? {} : { dispatchId }
|
|
3379
|
+
});
|
|
3380
|
+
}
|
|
3381
|
+
/** Restore only explicit source facts; never infer them from queue/trace IDs. */
|
|
3382
|
+
function restoreProvenance(value) {
|
|
3383
|
+
if (value === void 0) return emptyProvenance;
|
|
3384
|
+
const record = (v) => v != null && typeof v === "object" && !Array.isArray(v);
|
|
3385
|
+
const invalid = () => /* @__PURE__ */ new TypeError("Invalid provenance in queue metadata.");
|
|
3386
|
+
if (!record(value)) throw invalid();
|
|
3387
|
+
validateDispatchId(value.dispatchId);
|
|
3388
|
+
if (value.originatingRequestId !== void 0 && typeof value.originatingRequestId !== "string") throw invalid();
|
|
3389
|
+
let inboxRequest;
|
|
3390
|
+
if (value.inboxRequest !== void 0) {
|
|
3391
|
+
const source = value.inboxRequest;
|
|
3392
|
+
if (!record(source) || typeof source.inboxRequestId !== "string" || source.inboxRequestId.length < 1 || typeof source.requestId !== "string" || !record(source.context)) throw invalid();
|
|
3393
|
+
const facts = source.context;
|
|
3394
|
+
if (typeof facts.origin !== "string" || typeof facts.host !== "string" || typeof facts.hostname !== "string" || typeof facts.canonicalOrigin !== "string") throw invalid();
|
|
3395
|
+
inboxRequest = Object.freeze({
|
|
3396
|
+
inboxRequestId: source.inboxRequestId,
|
|
3397
|
+
requestId: source.requestId,
|
|
3398
|
+
context: Object.freeze({
|
|
3399
|
+
origin: facts.origin,
|
|
3400
|
+
host: facts.host,
|
|
3401
|
+
hostname: facts.hostname,
|
|
3402
|
+
canonicalOrigin: facts.canonicalOrigin
|
|
3403
|
+
})
|
|
3404
|
+
});
|
|
3405
|
+
}
|
|
3406
|
+
return Object.freeze({
|
|
3407
|
+
...value.dispatchId === void 0 ? {} : { dispatchId: value.dispatchId },
|
|
3408
|
+
...inboxRequest == null ? {} : { inboxRequest },
|
|
3409
|
+
...value.originatingRequestId === void 0 ? {} : { originatingRequestId: value.originatingRequestId }
|
|
3410
|
+
});
|
|
3411
|
+
}
|
|
3412
|
+
/** Omit absent provenance on legacy and independent producers. */
|
|
3413
|
+
function serializeProvenance(value) {
|
|
3414
|
+
const provenance = restoreProvenance(value);
|
|
3415
|
+
return provenance.dispatchId === void 0 && provenance.inboxRequest == null && provenance.originatingRequestId == null ? {} : { provenance };
|
|
3416
|
+
}
|
|
3417
|
+
function sourceLogContext(source) {
|
|
3418
|
+
return {
|
|
3419
|
+
dispatchId: source.dispatchId,
|
|
3420
|
+
sourceInboxRequestId: source.inboxRequest?.inboxRequestId,
|
|
3421
|
+
sourceRequestId: source.inboxRequest?.requestId ?? source.originatingRequestId
|
|
3422
|
+
};
|
|
3423
|
+
}
|
|
3424
|
+
//#endregion
|
|
2817
3425
|
//#region src/federation/inbox-authentication.ts
|
|
2818
3426
|
/**
|
|
2819
3427
|
* The evidence of an activity that has not been authenticated by Fedify.
|
|
@@ -3008,7 +3616,7 @@ function createAuthenticationEvidence(evidence) {
|
|
|
3008
3616
|
}
|
|
3009
3617
|
//#endregion
|
|
3010
3618
|
//#region src/federation/inbox.ts
|
|
3011
|
-
const Temporal$
|
|
3619
|
+
const Temporal$11 = require_http.Temporal;
|
|
3012
3620
|
/**
|
|
3013
3621
|
* Checks whether an activity's ID and all of its actor IDs have the same
|
|
3014
3622
|
* [FEP-fe34] origin. Activities without an ID have no origin to authenticate.
|
|
@@ -3188,6 +3796,7 @@ async function routeActivity({ context: ctx, json, originalJson, normalizedActiv
|
|
|
3188
3796
|
_opentelemetry_api.propagation.inject(_opentelemetry_api.context.active(), carrier);
|
|
3189
3797
|
message = {
|
|
3190
3798
|
type: "inbox",
|
|
3799
|
+
...serializeProvenance(getOutboundProvenance(ctx)),
|
|
3191
3800
|
id: crypto.randomUUID(),
|
|
3192
3801
|
baseUrl: ctx.origin,
|
|
3193
3802
|
activity: originalJson ?? json,
|
|
@@ -3298,7 +3907,7 @@ async function routeActivity({ context: ctx, json, originalJson, normalizedActiv
|
|
|
3298
3907
|
return "error";
|
|
3299
3908
|
}
|
|
3300
3909
|
require_http.recordInboxActivity(meterProvider, "processed", (0, _fedify_vocab.getTypeId)(activity).href);
|
|
3301
|
-
if (cacheKey != null) await kv.set(cacheKey, true, { ttl: Temporal$
|
|
3910
|
+
if (cacheKey != null) await kv.set(cacheKey, true, { ttl: Temporal$11.Duration.from({ days: 1 }) });
|
|
3302
3911
|
logger.info("Activity {activityId} has been processed.", {
|
|
3303
3912
|
activityId: activity.id?.href,
|
|
3304
3913
|
activity: json,
|
|
@@ -3686,7 +4295,7 @@ async function restoreVerifiedActivityView(view, message) {
|
|
|
3686
4295
|
}
|
|
3687
4296
|
//#endregion
|
|
3688
4297
|
//#region src/federation/keycache.ts
|
|
3689
|
-
const Temporal$
|
|
4298
|
+
const Temporal$10 = require_http.Temporal;
|
|
3690
4299
|
const KEY_CACHE_GENERATION = "3";
|
|
3691
4300
|
const COMPATIBLE_KEY_SEGMENT = "__compatible";
|
|
3692
4301
|
const MAX_OWNER_BYTES = 32 * 1024;
|
|
@@ -3707,8 +4316,8 @@ var KvKeyCache = class {
|
|
|
3707
4316
|
this.kv = kv;
|
|
3708
4317
|
this.prefix = prefix;
|
|
3709
4318
|
this.options = options;
|
|
3710
|
-
this.unavailableKeyTtl = options.unavailableKeyTtl ?? Temporal$
|
|
3711
|
-
this.keyTtl = options.keyTtl ?? Temporal$
|
|
4319
|
+
this.unavailableKeyTtl = options.unavailableKeyTtl ?? Temporal$10.Duration.from({ minutes: 10 });
|
|
4320
|
+
this.keyTtl = options.keyTtl ?? Temporal$10.Duration.from({ days: 30 });
|
|
3712
4321
|
this.nullKeys = /* @__PURE__ */ new Map();
|
|
3713
4322
|
}
|
|
3714
4323
|
#getFetchErrorKey(keyId) {
|
|
@@ -3728,13 +4337,13 @@ var KvKeyCache = class {
|
|
|
3728
4337
|
async get(keyId) {
|
|
3729
4338
|
const negativeExpiration = this.nullKeys.get(keyId.href);
|
|
3730
4339
|
if (negativeExpiration != null) {
|
|
3731
|
-
if (Temporal$
|
|
4340
|
+
if (Temporal$10.Now.instant().until(negativeExpiration).sign >= 0) return null;
|
|
3732
4341
|
this.nullKeys.delete(keyId.href);
|
|
3733
4342
|
}
|
|
3734
4343
|
const serialized = await this.kv.get(this.#entryKey(keyId));
|
|
3735
4344
|
if (serialized === void 0) return void 0;
|
|
3736
4345
|
if (serialized === null) {
|
|
3737
|
-
this.nullKeys.set(keyId.href, Temporal$
|
|
4346
|
+
this.nullKeys.set(keyId.href, Temporal$10.Now.instant().add(this.unavailableKeyTtl));
|
|
3738
4347
|
return null;
|
|
3739
4348
|
}
|
|
3740
4349
|
try {
|
|
@@ -3750,7 +4359,7 @@ var KvKeyCache = class {
|
|
|
3750
4359
|
}
|
|
3751
4360
|
async set(keyId, key) {
|
|
3752
4361
|
if (key == null) {
|
|
3753
|
-
this.nullKeys.set(keyId.href, Temporal$
|
|
4362
|
+
this.nullKeys.set(keyId.href, Temporal$10.Now.instant().add(this.unavailableKeyTtl));
|
|
3754
4363
|
await this.kv.set(this.#entryKey(keyId), null, { ttl: this.unavailableKeyTtl });
|
|
3755
4364
|
return;
|
|
3756
4365
|
}
|
|
@@ -3769,7 +4378,7 @@ var KvKeyCache = class {
|
|
|
3769
4378
|
* @internal
|
|
3770
4379
|
*/
|
|
3771
4380
|
compatibleKeyScope(scope) {
|
|
3772
|
-
const now = this.options.now ?? (() => Temporal$
|
|
4381
|
+
const now = this.options.now ?? (() => Temporal$10.Now.instant());
|
|
3773
4382
|
const entryKey = (keyId) => [
|
|
3774
4383
|
...this.prefix,
|
|
3775
4384
|
COMPATIBLE_KEY_SEGMENT,
|
|
@@ -3789,7 +4398,7 @@ var KvKeyCache = class {
|
|
|
3789
4398
|
if (entry.key === null) return null;
|
|
3790
4399
|
let expires;
|
|
3791
4400
|
try {
|
|
3792
|
-
expires = Temporal$
|
|
4401
|
+
expires = Temporal$10.Instant.fromEpochMilliseconds(entry.expires);
|
|
3793
4402
|
} catch (error) {
|
|
3794
4403
|
if (!(error instanceof RangeError)) throw error;
|
|
3795
4404
|
await this.kv.delete(entryKey(keyId));
|
|
@@ -3834,8 +4443,8 @@ var KvKeyCache = class {
|
|
|
3834
4443
|
key: serialized,
|
|
3835
4444
|
expires,
|
|
3836
4445
|
...owner == null ? {} : { owner }
|
|
3837
|
-
}, { ttl: Temporal$
|
|
3838
|
-
return key == null ? void 0 : Temporal$
|
|
4446
|
+
}, { ttl: Temporal$10.Duration.from({ milliseconds: ttl }) });
|
|
4447
|
+
return key == null ? void 0 : Temporal$10.Instant.fromEpochMilliseconds(expires);
|
|
3839
4448
|
},
|
|
3840
4449
|
delete: async (keyId) => {
|
|
3841
4450
|
await this.kv.delete(entryKey(keyId));
|
|
@@ -4353,11 +4962,15 @@ function extractInboxes({ recipients, preferSharedInbox, excludeBaseUris }) {
|
|
|
4353
4962
|
* @throws {Error} If the activity fails to send.
|
|
4354
4963
|
*/
|
|
4355
4964
|
function sendActivity(options, deps = {}) {
|
|
4965
|
+
options.attemptStarted?.(options.inbox.href);
|
|
4966
|
+
const attempt = options.observation?.startAttempt(options.inbox.href);
|
|
4967
|
+
const parentSpanContext = _opentelemetry_api.trace.getSpanContext(_opentelemetry_api.context.active());
|
|
4356
4968
|
const tracerProvider = options.tracerProvider ?? _opentelemetry_api.trace.getTracerProvider();
|
|
4357
4969
|
return tracerProvider.getTracer(require_http.name, require_http.version).startActiveSpan("activitypub.send_activity", {
|
|
4358
4970
|
kind: _opentelemetry_api.SpanKind.CLIENT,
|
|
4359
4971
|
attributes: { "activitypub.shared_inbox": options.sharedInbox ?? false }
|
|
4360
4972
|
}, async (span) => {
|
|
4973
|
+
attempt?.setSpan(span, parentSpanContext);
|
|
4361
4974
|
if (options.activityId != null) span.setAttribute("activitypub.activity.id", options.activityId);
|
|
4362
4975
|
if (options.activityType != null) span.setAttribute("activitypub.activity.type", options.activityType);
|
|
4363
4976
|
const spanContext = span.spanContext();
|
|
@@ -4368,12 +4981,14 @@ function sendActivity(options, deps = {}) {
|
|
|
4368
4981
|
}, () => sendActivityInternal({
|
|
4369
4982
|
...options,
|
|
4370
4983
|
tracerProvider
|
|
4371
|
-
}, span, deps));
|
|
4984
|
+
}, span, deps, attempt));
|
|
4985
|
+
await attempt?.succeeded();
|
|
4372
4986
|
} catch (e) {
|
|
4373
4987
|
span.setStatus({
|
|
4374
4988
|
code: _opentelemetry_api.SpanStatusCode.ERROR,
|
|
4375
4989
|
message: String(e)
|
|
4376
4990
|
});
|
|
4991
|
+
await attempt?.failed(e);
|
|
4377
4992
|
throw e;
|
|
4378
4993
|
} finally {
|
|
4379
4994
|
span.end();
|
|
@@ -4413,6 +5028,7 @@ async function sendActivityThroughGateways(parameters, inboxes) {
|
|
|
4413
5028
|
throw lastError;
|
|
4414
5029
|
}
|
|
4415
5030
|
const MAX_ERROR_RESPONSE_BODY_BYTES = 1024;
|
|
5031
|
+
const ERROR_RESPONSE_BODY_TIMEOUT_MS = 1e3;
|
|
4416
5032
|
function getActivityActorId(activity) {
|
|
4417
5033
|
if (!isRecord(activity)) return void 0;
|
|
4418
5034
|
return getIdValue(activity.actor);
|
|
@@ -4433,16 +5049,25 @@ function isRecord(value) {
|
|
|
4433
5049
|
return typeof value === "object" && value != null;
|
|
4434
5050
|
}
|
|
4435
5051
|
async function readLimitedResponseBody(response, maxBytes) {
|
|
5052
|
+
const captured = takeCapturedDiagnostic(response);
|
|
5053
|
+
if (captured !== void 0) return captured;
|
|
4436
5054
|
if (response.body == null) return "";
|
|
4437
|
-
const
|
|
5055
|
+
const pump = new ResponseBodyPump(response.body, ERROR_RESPONSE_BODY_TIMEOUT_MS);
|
|
4438
5056
|
const decoder = new TextDecoder();
|
|
4439
5057
|
const chunks = [];
|
|
4440
5058
|
let totalBytes = 0;
|
|
4441
5059
|
let truncated = false;
|
|
5060
|
+
let timedOut = false;
|
|
4442
5061
|
try {
|
|
4443
5062
|
while (true) {
|
|
4444
|
-
const
|
|
5063
|
+
const result = await pump.read();
|
|
5064
|
+
if (result == null) {
|
|
5065
|
+
timedOut = true;
|
|
5066
|
+
break;
|
|
5067
|
+
}
|
|
5068
|
+
const { done, value } = result;
|
|
4445
5069
|
if (done) break;
|
|
5070
|
+
if (value.length === 0) continue;
|
|
4446
5071
|
if (totalBytes + value.length > maxBytes) {
|
|
4447
5072
|
const remaining = maxBytes - totalBytes;
|
|
4448
5073
|
if (remaining > 0) chunks.push(decoder.decode(value.slice(0, remaining), { stream: true }));
|
|
@@ -4453,14 +5078,14 @@ async function readLimitedResponseBody(response, maxBytes) {
|
|
|
4453
5078
|
totalBytes += value.length;
|
|
4454
5079
|
}
|
|
4455
5080
|
} finally {
|
|
4456
|
-
|
|
4457
|
-
reader.releaseLock();
|
|
5081
|
+
pump.close();
|
|
4458
5082
|
}
|
|
4459
5083
|
let result = chunks.join("");
|
|
4460
5084
|
if (truncated) result += "… (truncated)";
|
|
5085
|
+
else if (timedOut) result += "… (timed out)";
|
|
4461
5086
|
return result;
|
|
4462
5087
|
}
|
|
4463
|
-
async function sendActivityInternal({ activity, activityId, activityType, keys, inbox, allowPrivateAddress, headers, specDeterminer, meterProvider, tracerProvider }, span, deps) {
|
|
5088
|
+
async function sendActivityInternal({ activity, activityId, activityType, keys, inbox, allowPrivateAddress, headers, specDeterminer, meterProvider, tracerProvider }, span, deps, attempt) {
|
|
4464
5089
|
const logger = (0, _logtape_logtape.getLogger)([
|
|
4465
5090
|
"fedify",
|
|
4466
5091
|
"federation",
|
|
@@ -4476,6 +5101,11 @@ async function sendActivityInternal({ activity, activityId, activityType, keys,
|
|
|
4476
5101
|
session = await openUnvalidatedHop(url);
|
|
4477
5102
|
} catch (error) {
|
|
4478
5103
|
if (error instanceof _fedify_vocab_runtime.UrlError && error.reason === "dns") {
|
|
5104
|
+
attempt?.failure({
|
|
5105
|
+
kind: "transport",
|
|
5106
|
+
phase: "validation",
|
|
5107
|
+
error
|
|
5108
|
+
});
|
|
4479
5109
|
logger.error("DNS resolution failed for URL: {url}", {
|
|
4480
5110
|
url,
|
|
4481
5111
|
error
|
|
@@ -4484,13 +5114,47 @@ async function sendActivityInternal({ activity, activityId, activityType, keys,
|
|
|
4484
5114
|
failure.cause = error;
|
|
4485
5115
|
throw failure;
|
|
4486
5116
|
}
|
|
4487
|
-
if (error instanceof _fedify_vocab_runtime.UrlError)
|
|
4488
|
-
|
|
4489
|
-
|
|
4490
|
-
|
|
5117
|
+
if (error instanceof _fedify_vocab_runtime.UrlError) {
|
|
5118
|
+
if (error.reason === "disallowed") attempt?.failure({
|
|
5119
|
+
kind: "policy",
|
|
5120
|
+
phase: "validation",
|
|
5121
|
+
error
|
|
5122
|
+
});
|
|
5123
|
+
logger.error("Disallowed private URL: {url}", {
|
|
5124
|
+
url,
|
|
5125
|
+
error
|
|
5126
|
+
});
|
|
5127
|
+
}
|
|
4491
5128
|
throw error;
|
|
4492
5129
|
}
|
|
4493
|
-
return session;
|
|
5130
|
+
if (attempt == null) return session;
|
|
5131
|
+
return {
|
|
5132
|
+
url: session.url,
|
|
5133
|
+
async fetch(input, init) {
|
|
5134
|
+
try {
|
|
5135
|
+
return await session.fetch(input, init);
|
|
5136
|
+
} catch (error) {
|
|
5137
|
+
attempt.failure({
|
|
5138
|
+
kind: "transport",
|
|
5139
|
+
phase: "request",
|
|
5140
|
+
error
|
|
5141
|
+
});
|
|
5142
|
+
throw error;
|
|
5143
|
+
}
|
|
5144
|
+
},
|
|
5145
|
+
async close() {
|
|
5146
|
+
try {
|
|
5147
|
+
await session.close();
|
|
5148
|
+
} catch (error) {
|
|
5149
|
+
attempt.failure({
|
|
5150
|
+
kind: "local",
|
|
5151
|
+
phase: "cleanup",
|
|
5152
|
+
error
|
|
5153
|
+
});
|
|
5154
|
+
throw error;
|
|
5155
|
+
}
|
|
5156
|
+
}
|
|
5157
|
+
};
|
|
4494
5158
|
}
|
|
4495
5159
|
let initialSession;
|
|
4496
5160
|
try {
|
|
@@ -4508,14 +5172,45 @@ async function sendActivityInternal({ activity, activityId, activityType, keys,
|
|
|
4508
5172
|
return openHop(url);
|
|
4509
5173
|
};
|
|
4510
5174
|
try {
|
|
4511
|
-
|
|
4512
|
-
|
|
4513
|
-
|
|
4514
|
-
|
|
4515
|
-
headers,
|
|
4516
|
-
|
|
4517
|
-
|
|
4518
|
-
|
|
5175
|
+
let request;
|
|
5176
|
+
let body;
|
|
5177
|
+
try {
|
|
5178
|
+
headers = new Headers(headers);
|
|
5179
|
+
headers.set("Content-Type", "application/activity+json");
|
|
5180
|
+
} catch (error) {
|
|
5181
|
+
attempt?.failure({
|
|
5182
|
+
kind: "local",
|
|
5183
|
+
phase: "request-preparation",
|
|
5184
|
+
error
|
|
5185
|
+
});
|
|
5186
|
+
throw error;
|
|
5187
|
+
}
|
|
5188
|
+
try {
|
|
5189
|
+
attempt?.snapshotSenderPayload(activity);
|
|
5190
|
+
body = JSON.stringify(activity);
|
|
5191
|
+
} catch (error) {
|
|
5192
|
+
attempt?.failure({
|
|
5193
|
+
kind: "local",
|
|
5194
|
+
phase: "serialization",
|
|
5195
|
+
error
|
|
5196
|
+
});
|
|
5197
|
+
throw error;
|
|
5198
|
+
}
|
|
5199
|
+
try {
|
|
5200
|
+
request = new Request(inbox, {
|
|
5201
|
+
method: "POST",
|
|
5202
|
+
headers,
|
|
5203
|
+
body,
|
|
5204
|
+
redirect: "manual"
|
|
5205
|
+
});
|
|
5206
|
+
} catch (error) {
|
|
5207
|
+
attempt?.failure({
|
|
5208
|
+
kind: "local",
|
|
5209
|
+
phase: "request-preparation",
|
|
5210
|
+
error
|
|
5211
|
+
});
|
|
5212
|
+
throw error;
|
|
5213
|
+
}
|
|
4519
5214
|
let rsaKey = null;
|
|
4520
5215
|
for (const key of keys) if (key.privateKey.algorithm.name === "RSASSA-PKCS1-v1_5") {
|
|
4521
5216
|
rsaKey = key;
|
|
@@ -4532,13 +5227,25 @@ async function sendActivityInternal({ activity, activityId, activityType, keys,
|
|
|
4532
5227
|
privateKey: pair.privateKey
|
|
4533
5228
|
}))
|
|
4534
5229
|
});
|
|
5230
|
+
const consume = (response) => {
|
|
5231
|
+
return consumeSendActivityResponse(response, inbox);
|
|
5232
|
+
};
|
|
4535
5233
|
let outcome;
|
|
4536
5234
|
try {
|
|
4537
|
-
outcome = rsaKey == null ? await fetchWithValidatedRedirects(request, openDeliveryHop,
|
|
5235
|
+
outcome = rsaKey == null ? await fetchWithValidatedRedirects(request, openDeliveryHop, consume, attempt) : await require_http.doubleKnockScoped(request, rsaKey, {
|
|
4538
5236
|
tracerProvider,
|
|
4539
5237
|
specDeterminer
|
|
4540
|
-
}, openDeliveryHop,
|
|
5238
|
+
}, openDeliveryHop, consume, attempt == null ? void 0 : {
|
|
5239
|
+
fetch: (request, submit) => attempt.fetch(request, submit),
|
|
5240
|
+
response: (response, disposition) => attempt.response(response, disposition),
|
|
5241
|
+
error: (phase, error) => attempt.failure({
|
|
5242
|
+
kind: "local",
|
|
5243
|
+
phase,
|
|
5244
|
+
error
|
|
5245
|
+
})
|
|
5246
|
+
});
|
|
4541
5247
|
} catch (error) {
|
|
5248
|
+
attempt?.unknown(error);
|
|
4542
5249
|
const failure = error instanceof _fedify_vocab_runtime.FetchError || error instanceof _fedify_vocab_runtime.UrlError ? error : createFetchError(inbox.href, error);
|
|
4543
5250
|
logger.error("Failed to send activity {activityId} to {inbox}:\n{error}", {
|
|
4544
5251
|
activityId,
|
|
@@ -4558,7 +5265,13 @@ async function sendActivityInternal({ activity, activityId, activityType, keys,
|
|
|
4558
5265
|
statusText,
|
|
4559
5266
|
error
|
|
4560
5267
|
});
|
|
4561
|
-
|
|
5268
|
+
const failure = new SendActivityError(inbox, status, `Failed to send activity ${activityId} to ${inbox.href} (${status} ${statusText}):\n${error}`, error, responseHeaders);
|
|
5269
|
+
attempt?.failure({
|
|
5270
|
+
kind: "http",
|
|
5271
|
+
phase: "response",
|
|
5272
|
+
error: failure
|
|
5273
|
+
});
|
|
5274
|
+
throw failure;
|
|
4562
5275
|
}
|
|
4563
5276
|
deliverySuccess = true;
|
|
4564
5277
|
const eventAttributes = {
|
|
@@ -4602,7 +5315,7 @@ function cancelResponseBody(response, url) {
|
|
|
4602
5315
|
/**
|
|
4603
5316
|
* Reads what is needed from the final response of a delivery while its
|
|
4604
5317
|
* session is open: the body of a successful response is cancelled, and at
|
|
4605
|
-
* most 1 KiB of an error response body is read.
|
|
5318
|
+
* most 1 KiB of an error response body is read within its diagnostic budget.
|
|
4606
5319
|
*/
|
|
4607
5320
|
async function consumeSendActivityResponse(response, inbox) {
|
|
4608
5321
|
if (response.ok) {
|
|
@@ -4632,9 +5345,10 @@ async function consumeSendActivityResponse(response, inbox) {
|
|
|
4632
5345
|
* @param request The request to send.
|
|
4633
5346
|
* @param openHop The function to open a session for each hop.
|
|
4634
5347
|
* @param consume The function to consume the final response.
|
|
5348
|
+
* @param attempt The in-process attempt observation, if registered.
|
|
4635
5349
|
* @returns The value returned by `consume`.
|
|
4636
5350
|
*/
|
|
4637
|
-
async function fetchWithValidatedRedirects(request, openHop, consume) {
|
|
5351
|
+
async function fetchWithValidatedRedirects(request, openHop, consume, attempt) {
|
|
4638
5352
|
const logger = (0, _logtape_logtape.getLogger)([
|
|
4639
5353
|
"fedify",
|
|
4640
5354
|
"federation",
|
|
@@ -4645,7 +5359,7 @@ async function fetchWithValidatedRedirects(request, openHop, consume) {
|
|
|
4645
5359
|
const session = await openHop(request.url);
|
|
4646
5360
|
let response;
|
|
4647
5361
|
try {
|
|
4648
|
-
response = await session.fetch(request, { redirect: "manual" });
|
|
5362
|
+
response = await (attempt == null ? session.fetch(request, { redirect: "manual" }) : attempt.fetch(request, () => session.fetch(request, { redirect: "manual" })));
|
|
4649
5363
|
} catch (error) {
|
|
4650
5364
|
await closeAfterError(session, logger);
|
|
4651
5365
|
throw error;
|
|
@@ -4657,17 +5371,21 @@ async function fetchWithValidatedRedirects(request, openHop, consume) {
|
|
|
4657
5371
|
303,
|
|
4658
5372
|
307,
|
|
4659
5373
|
308
|
|
4660
|
-
].includes(response.status) || location == null)
|
|
4661
|
-
|
|
4662
|
-
|
|
4663
|
-
|
|
4664
|
-
|
|
4665
|
-
error
|
|
4666
|
-
|
|
4667
|
-
|
|
4668
|
-
|
|
4669
|
-
|
|
5374
|
+
].includes(response.status) || location == null) {
|
|
5375
|
+
attempt?.response(response, "final");
|
|
5376
|
+
try {
|
|
5377
|
+
return await consume(response);
|
|
5378
|
+
} catch (error) {
|
|
5379
|
+
logger.debug("Failed to consume the response from {url}; closing the session: {error}", {
|
|
5380
|
+
url: request.url,
|
|
5381
|
+
error
|
|
5382
|
+
});
|
|
5383
|
+
throw error;
|
|
5384
|
+
} finally {
|
|
5385
|
+
await session.close();
|
|
5386
|
+
}
|
|
4670
5387
|
}
|
|
5388
|
+
attempt?.response(response, "intermediate");
|
|
4671
5389
|
cancelResponseBody(response, request.url);
|
|
4672
5390
|
await session.close();
|
|
4673
5391
|
if (redirects >= 20) throw new TypeError("Too many redirects");
|
|
@@ -4728,7 +5446,9 @@ var SendActivityError = class extends Error {
|
|
|
4728
5446
|
* The response body from the inbox, if any. Note that this may be
|
|
4729
5447
|
* truncated to a maximum of 1 KiB to prevent excessive memory consumption
|
|
4730
5448
|
* when remote servers return large error pages (e.g., Cloudflare error pages).
|
|
4731
|
-
* If truncated, the string will end with `"… (truncated)"`.
|
|
5449
|
+
* If truncated, the string will end with `"… (truncated)"`. Diagnostic reading
|
|
5450
|
+
* has a separate one-second budget; a deadline retains any partial preview and
|
|
5451
|
+
* appends `"… (timed out)"`. This is independent of application body capture.
|
|
4732
5452
|
*/
|
|
4733
5453
|
responseBody;
|
|
4734
5454
|
/**
|
|
@@ -4755,7 +5475,7 @@ var SendActivityError = class extends Error {
|
|
|
4755
5475
|
};
|
|
4756
5476
|
//#endregion
|
|
4757
5477
|
//#region src/federation/portable-inbox.ts
|
|
4758
|
-
const Temporal$
|
|
5478
|
+
const Temporal$9 = require_http.Temporal;
|
|
4759
5479
|
const MAX_TIMEOUT_MS = 2 ** 31 - 1;
|
|
4760
5480
|
/**
|
|
4761
5481
|
* Fills in the defaults of {@link PortableInboxForwardingOptions} and
|
|
@@ -4771,9 +5491,9 @@ const MAX_TIMEOUT_MS = 2 ** 31 - 1;
|
|
|
4771
5491
|
function resolvePortableInboxForwardingOptions(options = {}) {
|
|
4772
5492
|
const maxTargets = options.maxTargets ?? 10;
|
|
4773
5493
|
if (!Number.isInteger(maxTargets) || maxTargets < 0) throw new RangeError("portableInboxForwarding.maxTargets must be a non-negative integer.");
|
|
4774
|
-
const ttl = Temporal$
|
|
5494
|
+
const ttl = Temporal$9.Duration.from(options.ttl ?? { days: 30 });
|
|
4775
5495
|
if (ttl.total("millisecond") <= 0) throw new RangeError("portableInboxForwarding.ttl must be positive.");
|
|
4776
|
-
const deadline = Temporal$
|
|
5496
|
+
const deadline = Temporal$9.Duration.from(options.deadline ?? { seconds: 10 });
|
|
4777
5497
|
const deadlineMs = deadline.total("millisecond");
|
|
4778
5498
|
if (deadlineMs < 0 || deadlineMs > MAX_TIMEOUT_MS) throw new RangeError(`portableInboxForwarding.deadline must be between 0 and ${MAX_TIMEOUT_MS} milliseconds.`);
|
|
4779
5499
|
return {
|
|
@@ -4883,9 +5603,10 @@ function coversDelivery(signature) {
|
|
|
4883
5603
|
* other gateways, that gateway has the activity already, so it is claimed
|
|
4884
5604
|
* without forwarding the activity to it. See {@link identifySendingGateway}.
|
|
4885
5605
|
* @param parameters The parameters.
|
|
5606
|
+
* @param observation The in-process producer observation, if registered.
|
|
4886
5607
|
* @returns The target inbox URLs that the activity was handed off to.
|
|
4887
5608
|
*/
|
|
4888
|
-
async function forwardPortableInboxActivity(parameters) {
|
|
5609
|
+
async function forwardPortableInboxActivity(parameters, observation) {
|
|
4889
5610
|
const logger = (0, _logtape_logtape.getLogger)([
|
|
4890
5611
|
"fedify",
|
|
4891
5612
|
"federation",
|
|
@@ -4894,8 +5615,16 @@ async function forwardPortableInboxActivity(parameters) {
|
|
|
4894
5615
|
const { recipient, activityId, excludedOrigins, kv, kvPrefix, outboxQueue } = parameters;
|
|
4895
5616
|
const options = parameters.options ?? resolvePortableInboxForwardingOptions();
|
|
4896
5617
|
const { maxTargets, ttl } = options;
|
|
4897
|
-
if (kv.cas == null
|
|
5618
|
+
if (kv.cas == null) {
|
|
5619
|
+
if (observation != null) await observation.skipped("forwarding-unavailable");
|
|
5620
|
+
return [];
|
|
5621
|
+
}
|
|
5622
|
+
if (maxTargets < 1) {
|
|
5623
|
+
if (observation != null) await observation.skipped("forwarding-disabled");
|
|
5624
|
+
return [];
|
|
5625
|
+
}
|
|
4898
5626
|
const deadline = Date.now() + options.deadline.total("millisecond");
|
|
5627
|
+
observation?.setPhase("destination-planning");
|
|
4899
5628
|
const canonicalActivityId = require_http.getCanonicalPortableId(activityId) ?? activityId.href;
|
|
4900
5629
|
const claimKey = (gateway) => [
|
|
4901
5630
|
...kvPrefix,
|
|
@@ -4923,15 +5652,27 @@ async function forwardPortableInboxActivity(parameters) {
|
|
|
4923
5652
|
targets.length = maxTargets;
|
|
4924
5653
|
}
|
|
4925
5654
|
const claimed = [];
|
|
4926
|
-
for (const { gateway, inbox } of targets) if (await kv.cas(claimKey(gateway), void 0, true, { ttl }))
|
|
4927
|
-
|
|
5655
|
+
for (const { gateway, inbox } of targets) if (await kv.cas(claimKey(gateway), void 0, true, { ttl })) {
|
|
5656
|
+
claimed.push(inbox);
|
|
5657
|
+
if (observation != null) await observation.planDestination(inbox.href, {
|
|
5658
|
+
actorIds: [recipient.actorId.href],
|
|
5659
|
+
sharedInbox: false,
|
|
5660
|
+
portableInbox: recipient.canonicalInboxId,
|
|
5661
|
+
mode: "portable-replication"
|
|
5662
|
+
});
|
|
5663
|
+
}
|
|
5664
|
+
observation?.planned(claimed.length);
|
|
5665
|
+
if (claimed.length < 1) {
|
|
5666
|
+
if (observation != null) await observation.skipped("no-forwarding-targets");
|
|
5667
|
+
return [];
|
|
5668
|
+
}
|
|
4928
5669
|
logger.debug("Forwarding activity {activityId} from the portable inbox {inbox} to other gateways:\n{targets}", {
|
|
4929
5670
|
activityId: activityId.href,
|
|
4930
5671
|
inbox: recipient.canonicalInboxId,
|
|
4931
5672
|
targets: claimed.map((t) => t.href)
|
|
4932
5673
|
});
|
|
4933
|
-
if (outboxQueue == null) await forwardImmediately(parameters, claimed, deadline);
|
|
4934
|
-
else await enqueueForwarding(parameters, outboxQueue, claimed);
|
|
5674
|
+
if (outboxQueue == null) await forwardImmediately(parameters, claimed, deadline, observation);
|
|
5675
|
+
else await enqueueForwarding(parameters, outboxQueue, claimed, observation);
|
|
4935
5676
|
return claimed;
|
|
4936
5677
|
}
|
|
4937
5678
|
/**
|
|
@@ -5074,7 +5815,7 @@ async function resolveForwardingKey({ getKeys, activityId }) {
|
|
|
5074
5815
|
}
|
|
5075
5816
|
return key;
|
|
5076
5817
|
}
|
|
5077
|
-
async function forwardImmediately(parameters, inboxes, deadlineAt) {
|
|
5818
|
+
async function forwardImmediately(parameters, inboxes, deadlineAt, observation) {
|
|
5078
5819
|
const { activity, activityId, activityType, allowPrivateAddress, specDeterminer, meterProvider, tracerProvider } = parameters;
|
|
5079
5820
|
const logger = (0, _logtape_logtape.getLogger)([
|
|
5080
5821
|
"fedify",
|
|
@@ -5082,23 +5823,50 @@ async function forwardImmediately(parameters, inboxes, deadlineAt) {
|
|
|
5082
5823
|
"inbox"
|
|
5083
5824
|
]);
|
|
5084
5825
|
const deadline = Math.max(0, deadlineAt - Date.now());
|
|
5085
|
-
|
|
5086
|
-
|
|
5087
|
-
|
|
5088
|
-
|
|
5089
|
-
|
|
5090
|
-
|
|
5091
|
-
|
|
5092
|
-
|
|
5093
|
-
|
|
5094
|
-
|
|
5095
|
-
|
|
5096
|
-
|
|
5097
|
-
|
|
5098
|
-
|
|
5099
|
-
|
|
5100
|
-
|
|
5101
|
-
|
|
5826
|
+
observation?.setPhase("key-resolution");
|
|
5827
|
+
const forwarding = (async () => {
|
|
5828
|
+
try {
|
|
5829
|
+
const key = await resolveForwardingKey(parameters);
|
|
5830
|
+
observation?.setPhase("request-preparation");
|
|
5831
|
+
const sending = [];
|
|
5832
|
+
for (const inbox of inboxes) {
|
|
5833
|
+
const delivery = observation?.delivery(inbox.href);
|
|
5834
|
+
let pending;
|
|
5835
|
+
try {
|
|
5836
|
+
pending = sendActivity({
|
|
5837
|
+
observation: delivery,
|
|
5838
|
+
activity,
|
|
5839
|
+
activityId: activityId.href,
|
|
5840
|
+
activityType,
|
|
5841
|
+
keys: key == null ? [] : [key],
|
|
5842
|
+
inbox,
|
|
5843
|
+
allowPrivateAddress,
|
|
5844
|
+
specDeterminer,
|
|
5845
|
+
meterProvider,
|
|
5846
|
+
tracerProvider
|
|
5847
|
+
});
|
|
5848
|
+
} catch (error) {
|
|
5849
|
+
if (delivery != null) await delivery.failed(error);
|
|
5850
|
+
throw error;
|
|
5851
|
+
}
|
|
5852
|
+
const settled = pending.catch((error) => {
|
|
5853
|
+
logger.error("Failed to forward activity {activityId} to {inbox}; it will not be forwarded to the inbox again:\n{error}", {
|
|
5854
|
+
activityId: activityId.href,
|
|
5855
|
+
inbox: inbox.href,
|
|
5856
|
+
error
|
|
5857
|
+
});
|
|
5858
|
+
});
|
|
5859
|
+
if (observation != null) settled.catch(() => {});
|
|
5860
|
+
sending.push(settled);
|
|
5861
|
+
if (delivery != null) await delivery.handoff({ type: "direct" });
|
|
5862
|
+
}
|
|
5863
|
+
if (observation != null) await observation.prepared("delivery");
|
|
5864
|
+
await Promise.all(sending);
|
|
5865
|
+
} catch (error) {
|
|
5866
|
+
if (observation != null) await observation.failed(error);
|
|
5867
|
+
throw error;
|
|
5868
|
+
}
|
|
5869
|
+
})();
|
|
5102
5870
|
let timer;
|
|
5103
5871
|
const timedOut = await Promise.race([forwarding.then(() => false), new Promise((resolve) => {
|
|
5104
5872
|
timer = setTimeout(() => resolve(true), deadline);
|
|
@@ -5109,13 +5877,14 @@ async function forwardImmediately(parameters, inboxes, deadlineAt) {
|
|
|
5109
5877
|
deadline
|
|
5110
5878
|
});
|
|
5111
5879
|
}
|
|
5112
|
-
async function enqueueForwarding(parameters, outboxQueue, inboxes) {
|
|
5880
|
+
async function enqueueForwarding(parameters, outboxQueue, inboxes, observation) {
|
|
5113
5881
|
const { activity, activityId, activityType, baseUrl, startQueue, meterProvider } = parameters;
|
|
5114
5882
|
const logger = (0, _logtape_logtape.getLogger)([
|
|
5115
5883
|
"fedify",
|
|
5116
5884
|
"federation",
|
|
5117
5885
|
"inbox"
|
|
5118
5886
|
]);
|
|
5887
|
+
observation?.setPhase("key-resolution");
|
|
5119
5888
|
const key = await resolveForwardingKey(parameters);
|
|
5120
5889
|
const keys = [];
|
|
5121
5890
|
if (key != null) try {
|
|
@@ -5130,28 +5899,43 @@ async function enqueueForwarding(parameters, outboxQueue, inboxes) {
|
|
|
5130
5899
|
error
|
|
5131
5900
|
});
|
|
5132
5901
|
}
|
|
5902
|
+
observation?.setPhase("request-preparation");
|
|
5133
5903
|
startQueue?.();
|
|
5134
5904
|
const started = (/* @__PURE__ */ new Date()).toISOString();
|
|
5135
5905
|
const traceContext = {};
|
|
5136
5906
|
_opentelemetry_api.propagation.inject(_opentelemetry_api.context.active(), traceContext);
|
|
5907
|
+
let preparedCount = 0;
|
|
5137
5908
|
await Promise.all(inboxes.map(async (inbox) => {
|
|
5138
|
-
const
|
|
5139
|
-
|
|
5140
|
-
id: crypto.randomUUID(),
|
|
5141
|
-
baseUrl,
|
|
5142
|
-
keys,
|
|
5143
|
-
activity,
|
|
5144
|
-
activityId: activityId.href,
|
|
5145
|
-
activityType,
|
|
5146
|
-
inbox: inbox.href,
|
|
5147
|
-
sharedInbox: false,
|
|
5148
|
-
started,
|
|
5149
|
-
attempt: 0,
|
|
5150
|
-
headers: {},
|
|
5151
|
-
traceContext
|
|
5152
|
-
};
|
|
5909
|
+
const delivery = observation?.delivery(inbox.href);
|
|
5910
|
+
let message;
|
|
5153
5911
|
try {
|
|
5154
|
-
|
|
5912
|
+
message = {
|
|
5913
|
+
type: "outbox",
|
|
5914
|
+
...serializeProvenance(parameters.provenance),
|
|
5915
|
+
id: crypto.randomUUID(),
|
|
5916
|
+
baseUrl,
|
|
5917
|
+
keys,
|
|
5918
|
+
activity,
|
|
5919
|
+
activityId: activityId.href,
|
|
5920
|
+
activityType,
|
|
5921
|
+
inbox: inbox.href,
|
|
5922
|
+
...observation?.continuation(inbox.href),
|
|
5923
|
+
sharedInbox: false,
|
|
5924
|
+
started,
|
|
5925
|
+
attempt: 0,
|
|
5926
|
+
headers: {},
|
|
5927
|
+
traceContext
|
|
5928
|
+
};
|
|
5929
|
+
} catch (error) {
|
|
5930
|
+
if (delivery != null) await delivery.failed(error);
|
|
5931
|
+
throw error;
|
|
5932
|
+
}
|
|
5933
|
+
if (observation != null && ++preparedCount === inboxes.length) await observation.prepared("delivery");
|
|
5934
|
+
try {
|
|
5935
|
+
await (observation == null ? outboxQueue.enqueue(message) : observation.enqueue([{
|
|
5936
|
+
inbox: inbox.href,
|
|
5937
|
+
queueMessageId: message.id
|
|
5938
|
+
}], "outbox", () => outboxQueue.enqueue(message)));
|
|
5155
5939
|
} catch (error) {
|
|
5156
5940
|
logger.error("Failed to enqueue activity {activityId} to forward to {inbox}; it will not be forwarded to the inbox again:\n{error}", {
|
|
5157
5941
|
activityId: activityId.href,
|
|
@@ -5165,7 +5949,7 @@ async function enqueueForwarding(parameters, outboxQueue, inboxes) {
|
|
|
5165
5949
|
}
|
|
5166
5950
|
//#endregion
|
|
5167
5951
|
//#region src/federation/temporal.ts
|
|
5168
|
-
const Temporal$
|
|
5952
|
+
const Temporal$8 = require_http.Temporal;
|
|
5169
5953
|
function isPlainObject$1(value) {
|
|
5170
5954
|
return typeof value === "object" && value != null && !Array.isArray(value);
|
|
5171
5955
|
}
|
|
@@ -5175,7 +5959,7 @@ function normalizeDateTimeLiteral(value) {
|
|
|
5175
5959
|
function isMalformedDateTimeLiteral(value) {
|
|
5176
5960
|
if (typeof value !== "string") return false;
|
|
5177
5961
|
try {
|
|
5178
|
-
Temporal$
|
|
5962
|
+
Temporal$8.Instant.from(normalizeDateTimeLiteral(value));
|
|
5179
5963
|
return false;
|
|
5180
5964
|
} catch {
|
|
5181
5965
|
return true;
|
|
@@ -5184,7 +5968,7 @@ function isMalformedDateTimeLiteral(value) {
|
|
|
5184
5968
|
function isMalformedDurationLiteral(value) {
|
|
5185
5969
|
if (typeof value !== "string") return false;
|
|
5186
5970
|
try {
|
|
5187
|
-
Temporal$
|
|
5971
|
+
Temporal$8.Duration.from(value);
|
|
5188
5972
|
return false;
|
|
5189
5973
|
} catch {
|
|
5190
5974
|
return true;
|
|
@@ -5255,7 +6039,7 @@ async function hasMalformedKnownTemporalLiteral(value, contextLoader) {
|
|
|
5255
6039
|
}
|
|
5256
6040
|
//#endregion
|
|
5257
6041
|
//#region src/federation/handler.ts
|
|
5258
|
-
const Temporal$
|
|
6042
|
+
const Temporal$7 = require_http.Temporal;
|
|
5259
6043
|
const rawInboxContextFactorySymbol = Symbol("fedify.rawInboxContextFactory");
|
|
5260
6044
|
const INBOX_COMPOUND_PROOF_LIMITS = {
|
|
5261
6045
|
maxDepth: 64,
|
|
@@ -7781,7 +8565,7 @@ async function buildAcceptSignatureHeader(policy, kv, noncePrefix) {
|
|
|
7781
8565
|
}
|
|
7782
8566
|
async function setKey(kv, key, policy) {
|
|
7783
8567
|
const seconds = policy.nonceTtlSeconds ?? 300;
|
|
7784
|
-
const ttl = Temporal$
|
|
8568
|
+
const ttl = Temporal$7.Duration.from({ seconds });
|
|
7785
8569
|
await kv.set(key, true, { ttl });
|
|
7786
8570
|
}
|
|
7787
8571
|
const DEF_COMPONENTS = [
|
|
@@ -7871,7 +8655,7 @@ async function verifyPortableActivityId(activityId, jsonLd, options) {
|
|
|
7871
8655
|
}
|
|
7872
8656
|
//#endregion
|
|
7873
8657
|
//#region src/federation/kv.ts
|
|
7874
|
-
const Temporal$
|
|
8658
|
+
const Temporal$6 = require_http.Temporal;
|
|
7875
8659
|
/**
|
|
7876
8660
|
* A key–value store that stores values in memory.
|
|
7877
8661
|
* Do not use this in production as it does not persist values.
|
|
@@ -7891,7 +8675,7 @@ var MemoryKvStore = class {
|
|
|
7891
8675
|
const entry = this.#values[encodedKey];
|
|
7892
8676
|
if (entry == null) return Promise.resolve(void 0);
|
|
7893
8677
|
const [value, expiration] = entry;
|
|
7894
|
-
if (expiration != null && Temporal$
|
|
8678
|
+
if (expiration != null && Temporal$6.Now.instant().until(expiration).sign < 0) {
|
|
7895
8679
|
delete this.#values[encodedKey];
|
|
7896
8680
|
return Promise.resolve(void 0);
|
|
7897
8681
|
}
|
|
@@ -7902,7 +8686,7 @@ var MemoryKvStore = class {
|
|
|
7902
8686
|
*/
|
|
7903
8687
|
set(key, value, options) {
|
|
7904
8688
|
const encodedKey = this.#encodeKey(key);
|
|
7905
|
-
const expiration = options?.ttl == null ? null : Temporal$
|
|
8689
|
+
const expiration = options?.ttl == null ? null : Temporal$6.Now.instant().add(options.ttl.round({ largestUnit: "hour" }));
|
|
7906
8690
|
this.#values[encodedKey] = [value, expiration];
|
|
7907
8691
|
return Promise.resolve();
|
|
7908
8692
|
}
|
|
@@ -7924,13 +8708,13 @@ var MemoryKvStore = class {
|
|
|
7924
8708
|
if (entry == null) currentValue = void 0;
|
|
7925
8709
|
else {
|
|
7926
8710
|
const [value, expiration] = entry;
|
|
7927
|
-
if (expiration != null && Temporal$
|
|
8711
|
+
if (expiration != null && Temporal$6.Now.instant().until(expiration).sign < 0) {
|
|
7928
8712
|
delete this.#values[encodedKey];
|
|
7929
8713
|
currentValue = void 0;
|
|
7930
8714
|
} else currentValue = value;
|
|
7931
8715
|
}
|
|
7932
8716
|
if (!(0, es_toolkit.isEqual)(currentValue, expectedValue)) return Promise.resolve(false);
|
|
7933
|
-
const expiration = options?.ttl == null ? null : Temporal$
|
|
8717
|
+
const expiration = options?.ttl == null ? null : Temporal$6.Now.instant().add(options.ttl.round({ largestUnit: "hour" }));
|
|
7934
8718
|
this.#values[encodedKey] = [newValue, expiration];
|
|
7935
8719
|
return Promise.resolve(true);
|
|
7936
8720
|
}
|
|
@@ -7938,7 +8722,7 @@ var MemoryKvStore = class {
|
|
|
7938
8722
|
* {@inheritDoc KvStore.list}
|
|
7939
8723
|
*/
|
|
7940
8724
|
async *list(prefix) {
|
|
7941
|
-
const now = Temporal$
|
|
8725
|
+
const now = Temporal$6.Now.instant();
|
|
7942
8726
|
for (const [encodedKey, entry] of Object.entries(this.#values)) {
|
|
7943
8727
|
const key = JSON.parse(encodedKey);
|
|
7944
8728
|
if (prefix != null) {
|
|
@@ -7958,6 +8742,787 @@ var MemoryKvStore = class {
|
|
|
7958
8742
|
}
|
|
7959
8743
|
};
|
|
7960
8744
|
//#endregion
|
|
8745
|
+
//#region src/federation/outbound-observation.ts
|
|
8746
|
+
const Temporal$5 = require_http.Temporal;
|
|
8747
|
+
function snapshotDeliveryIdentity(identity) {
|
|
8748
|
+
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.");
|
|
8749
|
+
return Object.freeze({
|
|
8750
|
+
broadcast: Object.freeze(identity.broadcast.type === "known" ? {
|
|
8751
|
+
type: "known",
|
|
8752
|
+
id: identity.broadcast.id
|
|
8753
|
+
} : { type: "unknown" }),
|
|
8754
|
+
deliveryId: identity.deliveryId,
|
|
8755
|
+
destinationInbox: identity.destinationInbox,
|
|
8756
|
+
...identity.portableInbox == null ? {} : { portableInbox: identity.portableInbox }
|
|
8757
|
+
});
|
|
8758
|
+
}
|
|
8759
|
+
async function legacyFanoutIdentity(taskId, inbox, portableInbox) {
|
|
8760
|
+
const bytes = new TextEncoder().encode(JSON.stringify([
|
|
8761
|
+
"fedify-legacy-fanout-delivery",
|
|
8762
|
+
taskId,
|
|
8763
|
+
inbox
|
|
8764
|
+
]));
|
|
8765
|
+
const digest = await crypto.subtle.digest("SHA-256", bytes);
|
|
8766
|
+
return {
|
|
8767
|
+
broadcast: { type: "unknown" },
|
|
8768
|
+
deliveryId: `legacy-fanout:${(0, byte_encodings_hex.encodeHex)(new Uint8Array(digest))}`,
|
|
8769
|
+
destinationInbox: inbox,
|
|
8770
|
+
...portableInbox == null ? {} : { portableInbox }
|
|
8771
|
+
};
|
|
8772
|
+
}
|
|
8773
|
+
async function notifyObserver(context, callback, event, unit) {
|
|
8774
|
+
if (callback == null) return;
|
|
8775
|
+
try {
|
|
8776
|
+
await (0, _logtape_logtape.withContext)(sourceLogContext(event.provenance), () => callback(context, event));
|
|
8777
|
+
} catch (error) {
|
|
8778
|
+
logObserverFailure(unit, error);
|
|
8779
|
+
}
|
|
8780
|
+
}
|
|
8781
|
+
function logObserverFailure(unit, error) {
|
|
8782
|
+
try {
|
|
8783
|
+
(0, _logtape_logtape.getLogger)([
|
|
8784
|
+
"fedify",
|
|
8785
|
+
"federation",
|
|
8786
|
+
"outbox"
|
|
8787
|
+
]).error("An unexpected error occurred in outbound {unit} observer:\n{error}", {
|
|
8788
|
+
unit,
|
|
8789
|
+
error
|
|
8790
|
+
});
|
|
8791
|
+
} catch {}
|
|
8792
|
+
}
|
|
8793
|
+
function warnInvalidDeliveryIdentity(messageId, error) {
|
|
8794
|
+
try {
|
|
8795
|
+
(0, _logtape_logtape.getLogger)([
|
|
8796
|
+
"fedify",
|
|
8797
|
+
"federation",
|
|
8798
|
+
"outbox"
|
|
8799
|
+
]).warn("Ignoring invalid outbound delivery identity in queue message {messageId}: {error}", {
|
|
8800
|
+
messageId,
|
|
8801
|
+
error
|
|
8802
|
+
});
|
|
8803
|
+
} catch {}
|
|
8804
|
+
}
|
|
8805
|
+
function snapshotTrace(span, parentSpanContext) {
|
|
8806
|
+
const spanContext = span.spanContext();
|
|
8807
|
+
if (!(0, _opentelemetry_api.isSpanContextValid)(spanContext) || spanContext.isRemote || spanContext.traceId === parentSpanContext?.traceId && spanContext.spanId === parentSpanContext.spanId) return void 0;
|
|
8808
|
+
return Object.freeze({
|
|
8809
|
+
traceId: spanContext.traceId,
|
|
8810
|
+
spanId: spanContext.spanId
|
|
8811
|
+
});
|
|
8812
|
+
}
|
|
8813
|
+
/** State owned by one concrete send, never persisted in queue metadata. */
|
|
8814
|
+
var OutboundAttemptObservation = class {
|
|
8815
|
+
inboxUrl;
|
|
8816
|
+
report;
|
|
8817
|
+
reportExchange;
|
|
8818
|
+
headerPolicies;
|
|
8819
|
+
bodyCapture;
|
|
8820
|
+
senderPayload;
|
|
8821
|
+
#attemptId = crypto.randomUUID();
|
|
8822
|
+
#failures = [];
|
|
8823
|
+
#response = Object.freeze({ type: "not-observed" });
|
|
8824
|
+
#finalResponse;
|
|
8825
|
+
#responses = /* @__PURE__ */ new WeakMap();
|
|
8826
|
+
#trace;
|
|
8827
|
+
#finished = false;
|
|
8828
|
+
#exchangeCount = 0;
|
|
8829
|
+
#senderPayload;
|
|
8830
|
+
constructor(inboxUrl, report, reportExchange = void 0, headerPolicies = {}, bodyCapture = void 0, senderPayload = false) {
|
|
8831
|
+
this.inboxUrl = inboxUrl;
|
|
8832
|
+
this.report = report;
|
|
8833
|
+
this.reportExchange = reportExchange;
|
|
8834
|
+
this.headerPolicies = headerPolicies;
|
|
8835
|
+
this.bodyCapture = bodyCapture;
|
|
8836
|
+
this.senderPayload = senderPayload;
|
|
8837
|
+
this.#senderPayload = Object.freeze({
|
|
8838
|
+
stage: "sender-payload",
|
|
8839
|
+
type: "unavailable",
|
|
8840
|
+
reason: senderPayload ? "not-materialized" : "disabled"
|
|
8841
|
+
});
|
|
8842
|
+
}
|
|
8843
|
+
snapshotSenderPayload(input) {
|
|
8844
|
+
if (this.senderPayload) this.#senderPayload = projectSenderPayload(input);
|
|
8845
|
+
}
|
|
8846
|
+
setSpan(span, parentSpanContext) {
|
|
8847
|
+
this.#trace = snapshotTrace(span, parentSpanContext);
|
|
8848
|
+
}
|
|
8849
|
+
async fetch(request, submit) {
|
|
8850
|
+
const exchangeIndex = this.#exchangeCount++;
|
|
8851
|
+
const snapshot = Object.freeze({
|
|
8852
|
+
url: request.url,
|
|
8853
|
+
method: request.method,
|
|
8854
|
+
...this.headerPolicies.requestHeaders == null ? {} : { headers: snapshotHeaders(request.headers, this.headerPolicies.requestHeaders) }
|
|
8855
|
+
});
|
|
8856
|
+
let response;
|
|
8857
|
+
try {
|
|
8858
|
+
response = await submit();
|
|
8859
|
+
} catch (error) {
|
|
8860
|
+
await this.#exchange(exchangeIndex, snapshot, {
|
|
8861
|
+
type: "failed",
|
|
8862
|
+
error
|
|
8863
|
+
});
|
|
8864
|
+
throw error;
|
|
8865
|
+
}
|
|
8866
|
+
const metadata = Object.freeze({
|
|
8867
|
+
status: response.status,
|
|
8868
|
+
statusText: response.statusText,
|
|
8869
|
+
...response.url === "" ? {} : { url: response.url }
|
|
8870
|
+
});
|
|
8871
|
+
const observed = Object.freeze({
|
|
8872
|
+
exchangeIndex,
|
|
8873
|
+
metadata
|
|
8874
|
+
});
|
|
8875
|
+
this.#responses.set(response, observed);
|
|
8876
|
+
this.#response = Object.freeze({
|
|
8877
|
+
type: "undetermined",
|
|
8878
|
+
lastObserved: observed
|
|
8879
|
+
});
|
|
8880
|
+
const responseSnapshot = Object.freeze({
|
|
8881
|
+
...metadata,
|
|
8882
|
+
...this.headerPolicies.responseHeaders == null ? {} : { headers: snapshotHeaders(response.headers, this.headerPolicies.responseHeaders) }
|
|
8883
|
+
});
|
|
8884
|
+
const bodyCapture = this.bodyCapture == null ? void 0 : await captureResponseBody(response, this.bodyCapture, (body, signal) => this.bodyCapture.onBody({
|
|
8885
|
+
attemptId: this.#attemptId,
|
|
8886
|
+
inboxUrl: this.inboxUrl,
|
|
8887
|
+
exchangeIndex,
|
|
8888
|
+
observedAt: Temporal$5.Now.instant().epochMilliseconds,
|
|
8889
|
+
request: snapshot,
|
|
8890
|
+
response: responseSnapshot,
|
|
8891
|
+
body,
|
|
8892
|
+
signal
|
|
8893
|
+
}));
|
|
8894
|
+
if (bodyCapture?.type === "failed" && bodyCapture.phase === "observer") logObserverFailure("response-body", bodyCapture.error);
|
|
8895
|
+
await this.#exchange(exchangeIndex, snapshot, {
|
|
8896
|
+
type: "response",
|
|
8897
|
+
response: responseSnapshot
|
|
8898
|
+
}, bodyCapture);
|
|
8899
|
+
return response;
|
|
8900
|
+
}
|
|
8901
|
+
#exchange(exchangeIndex, request, outcome, bodyCapture) {
|
|
8902
|
+
return this.reportExchange?.({
|
|
8903
|
+
attemptId: this.#attemptId,
|
|
8904
|
+
inboxUrl: this.inboxUrl,
|
|
8905
|
+
exchangeIndex,
|
|
8906
|
+
observedAt: Temporal$5.Now.instant().epochMilliseconds,
|
|
8907
|
+
request,
|
|
8908
|
+
outcome: Object.freeze(outcome),
|
|
8909
|
+
...bodyCapture == null ? {} : { bodyCapture }
|
|
8910
|
+
}) ?? Promise.resolve();
|
|
8911
|
+
}
|
|
8912
|
+
response(response, disposition) {
|
|
8913
|
+
const observed = this.#responses.get(response);
|
|
8914
|
+
if (observed == null) return;
|
|
8915
|
+
if (disposition === "final") {
|
|
8916
|
+
this.#finalResponse = Object.freeze({
|
|
8917
|
+
type: "final",
|
|
8918
|
+
...observed
|
|
8919
|
+
});
|
|
8920
|
+
this.#response = this.#finalResponse;
|
|
8921
|
+
} else this.#response = Object.freeze({
|
|
8922
|
+
type: "intermediate-only",
|
|
8923
|
+
lastObserved: observed
|
|
8924
|
+
});
|
|
8925
|
+
}
|
|
8926
|
+
failure(failure) {
|
|
8927
|
+
this.#failures.push(Object.freeze(failure));
|
|
8928
|
+
}
|
|
8929
|
+
unknown(error) {
|
|
8930
|
+
if (!this.#failures.some((failure) => failure.error === error || failure.phase !== "cleanup")) this.failure({
|
|
8931
|
+
kind: "unknown",
|
|
8932
|
+
phase: "unknown",
|
|
8933
|
+
error
|
|
8934
|
+
});
|
|
8935
|
+
}
|
|
8936
|
+
succeeded() {
|
|
8937
|
+
if (this.#finished) return Promise.resolve();
|
|
8938
|
+
if (this.#finalResponse == null) throw new Error("A successful outbound attempt must have an observed final response.");
|
|
8939
|
+
return this.#finish({
|
|
8940
|
+
type: "succeeded",
|
|
8941
|
+
response: this.#finalResponse
|
|
8942
|
+
});
|
|
8943
|
+
}
|
|
8944
|
+
failed(error) {
|
|
8945
|
+
let failure;
|
|
8946
|
+
let diagnostics;
|
|
8947
|
+
if (this.#response.type === "final" && (this.#response.metadata.status < 200 || this.#response.metadata.status >= 300)) {
|
|
8948
|
+
failure = this.#failures.find((failure) => failure.kind === "http") ?? Object.freeze({
|
|
8949
|
+
kind: "http",
|
|
8950
|
+
phase: "response"
|
|
8951
|
+
});
|
|
8952
|
+
diagnostics = this.#failures.filter((failure) => failure.kind !== "http");
|
|
8953
|
+
} else {
|
|
8954
|
+
failure = this.#failures.find((failure) => failure.phase !== "cleanup") ?? this.#failures[0] ?? Object.freeze({
|
|
8955
|
+
kind: "unknown",
|
|
8956
|
+
phase: "unknown",
|
|
8957
|
+
error
|
|
8958
|
+
});
|
|
8959
|
+
diagnostics = this.#failures.filter((item) => item !== failure);
|
|
8960
|
+
}
|
|
8961
|
+
return this.#finish({
|
|
8962
|
+
type: "failed",
|
|
8963
|
+
failure,
|
|
8964
|
+
error,
|
|
8965
|
+
response: this.#response,
|
|
8966
|
+
...diagnostics.length === 0 ? {} : { diagnostics: Object.freeze(diagnostics) }
|
|
8967
|
+
});
|
|
8968
|
+
}
|
|
8969
|
+
#finish(outcome) {
|
|
8970
|
+
if (this.#finished) return Promise.resolve();
|
|
8971
|
+
this.#finished = true;
|
|
8972
|
+
return this.report({
|
|
8973
|
+
attemptId: this.#attemptId,
|
|
8974
|
+
inboxUrl: this.inboxUrl,
|
|
8975
|
+
httpExchangeCount: this.#exchangeCount,
|
|
8976
|
+
senderPayload: this.#senderPayload,
|
|
8977
|
+
observedAt: Temporal$5.Now.instant().epochMilliseconds,
|
|
8978
|
+
...this.#trace == null ? {} : { trace: this.#trace },
|
|
8979
|
+
outcome: Object.freeze(outcome)
|
|
8980
|
+
});
|
|
8981
|
+
}
|
|
8982
|
+
};
|
|
8983
|
+
function snapshotOutboxIdentity(message) {
|
|
8984
|
+
if (message.outbound != null) try {
|
|
8985
|
+
return snapshotDeliveryIdentity(message.outbound);
|
|
8986
|
+
} catch (error) {
|
|
8987
|
+
warnInvalidDeliveryIdentity(message.id, error);
|
|
8988
|
+
}
|
|
8989
|
+
return Object.freeze({
|
|
8990
|
+
broadcast: Object.freeze({ type: "unknown" }),
|
|
8991
|
+
deliveryId: message.id,
|
|
8992
|
+
...message.portableInbox == null ? {} : { portableInbox: message.portableInbox }
|
|
8993
|
+
});
|
|
8994
|
+
}
|
|
8995
|
+
/** Per-execution state, independent of concrete attempt/exchange callbacks. */
|
|
8996
|
+
var OutboundWorkerObservation = class {
|
|
8997
|
+
context;
|
|
8998
|
+
callback;
|
|
8999
|
+
provenance;
|
|
9000
|
+
#attemptsStarted = 0;
|
|
9001
|
+
#actions;
|
|
9002
|
+
#trace;
|
|
9003
|
+
#preparationFailure;
|
|
9004
|
+
#preparationFailures = [];
|
|
9005
|
+
#queue;
|
|
9006
|
+
#delivery;
|
|
9007
|
+
#role;
|
|
9008
|
+
#currentContext;
|
|
9009
|
+
constructor(context, callback, message, provenance) {
|
|
9010
|
+
this.context = context;
|
|
9011
|
+
this.callback = callback;
|
|
9012
|
+
this.provenance = provenance;
|
|
9013
|
+
this.#role = message.type;
|
|
9014
|
+
this.#queue = Object.freeze({
|
|
9015
|
+
queueMessageId: message.id,
|
|
9016
|
+
...message.type === "outbox" ? { queueAttempt: message.attempt } : {}
|
|
9017
|
+
});
|
|
9018
|
+
if (message.type === "outbox") this.#delivery = snapshotOutboxIdentity(message);
|
|
9019
|
+
}
|
|
9020
|
+
attemptStarted = () => {
|
|
9021
|
+
this.#attemptsStarted++;
|
|
9022
|
+
};
|
|
9023
|
+
enableActions() {
|
|
9024
|
+
this.#actions ??= [];
|
|
9025
|
+
}
|
|
9026
|
+
directAttemptStarted(inboxUrl, identity) {
|
|
9027
|
+
this.attemptStarted();
|
|
9028
|
+
this.#actions?.push(Object.freeze({
|
|
9029
|
+
type: "handoff",
|
|
9030
|
+
action: "direct",
|
|
9031
|
+
inboxUrls: Object.freeze([inboxUrl]),
|
|
9032
|
+
...identity == null ? {} : { delivery: snapshotDeliveryIdentity(identity) }
|
|
9033
|
+
}));
|
|
9034
|
+
}
|
|
9035
|
+
async enqueueChildren(messages, enqueue) {
|
|
9036
|
+
if (this.#actions == null) return await enqueue();
|
|
9037
|
+
const facts = messages.map((message) => ({
|
|
9038
|
+
action: "enqueue",
|
|
9039
|
+
queue: Object.freeze({
|
|
9040
|
+
queueMessageId: message.id,
|
|
9041
|
+
queueAttempt: message.attempt
|
|
9042
|
+
}),
|
|
9043
|
+
inboxUrls: Object.freeze(message.gatewayInboxes != null && message.gatewayInboxes.length > 1 ? [...message.gatewayInboxes] : [message.inbox]),
|
|
9044
|
+
...message.outbound == null ? {} : { delivery: snapshotDeliveryIdentity(message.outbound) }
|
|
9045
|
+
}));
|
|
9046
|
+
try {
|
|
9047
|
+
await enqueue();
|
|
9048
|
+
} catch (error) {
|
|
9049
|
+
for (const fact of facts) this.#actions.push(Object.freeze({
|
|
9050
|
+
type: "action-failed",
|
|
9051
|
+
...fact,
|
|
9052
|
+
acceptance: "unknown",
|
|
9053
|
+
error
|
|
9054
|
+
}));
|
|
9055
|
+
throw error;
|
|
9056
|
+
}
|
|
9057
|
+
for (const fact of facts) this.#actions.push(Object.freeze({
|
|
9058
|
+
type: "scheduled",
|
|
9059
|
+
...fact
|
|
9060
|
+
}));
|
|
9061
|
+
}
|
|
9062
|
+
async enqueue(action, message, delay, enqueue, preparationFailures) {
|
|
9063
|
+
if (this.#actions == null) return await enqueue();
|
|
9064
|
+
const facts = {
|
|
9065
|
+
action,
|
|
9066
|
+
queue: Object.freeze({
|
|
9067
|
+
queueMessageId: message.id,
|
|
9068
|
+
queueAttempt: message.attempt
|
|
9069
|
+
}),
|
|
9070
|
+
inboxUrls: Object.freeze(message.gatewayInboxes != null && message.gatewayInboxes.length > 1 ? [...message.gatewayInboxes] : [message.inbox]),
|
|
9071
|
+
delay: delay.toString(),
|
|
9072
|
+
...preparationFailures == null || preparationFailures.length === 0 ? {} : { preparationFailures: Object.freeze([...preparationFailures]) },
|
|
9073
|
+
...message.gatewayNotBefore == null ? {} : { notBefore: Object.freeze({ ...message.gatewayNotBefore }) }
|
|
9074
|
+
};
|
|
9075
|
+
try {
|
|
9076
|
+
await enqueue();
|
|
9077
|
+
} catch (error) {
|
|
9078
|
+
this.#actions.push(Object.freeze({
|
|
9079
|
+
type: "action-failed",
|
|
9080
|
+
...facts,
|
|
9081
|
+
acceptance: "unknown",
|
|
9082
|
+
error
|
|
9083
|
+
}));
|
|
9084
|
+
throw error;
|
|
9085
|
+
}
|
|
9086
|
+
this.#actions.push(Object.freeze({
|
|
9087
|
+
type: "scheduled",
|
|
9088
|
+
...facts
|
|
9089
|
+
}));
|
|
9090
|
+
}
|
|
9091
|
+
abandon(reason, inboxUrls, preparationFailures) {
|
|
9092
|
+
this.#actions?.push(Object.freeze({
|
|
9093
|
+
type: "abandoned",
|
|
9094
|
+
reason,
|
|
9095
|
+
inboxUrls: Object.freeze([...inboxUrls]),
|
|
9096
|
+
...preparationFailures == null || preparationFailures.length === 0 ? {} : { preparationFailures: Object.freeze([...preparationFailures]) }
|
|
9097
|
+
}));
|
|
9098
|
+
}
|
|
9099
|
+
dropFailure(inboxUrls, error) {
|
|
9100
|
+
this.#actions?.push(Object.freeze({
|
|
9101
|
+
type: "action-failed",
|
|
9102
|
+
action: "drop",
|
|
9103
|
+
inboxUrls: Object.freeze([...inboxUrls]),
|
|
9104
|
+
error
|
|
9105
|
+
}));
|
|
9106
|
+
}
|
|
9107
|
+
async drop(inboxUrls, operation) {
|
|
9108
|
+
try {
|
|
9109
|
+
await operation();
|
|
9110
|
+
} catch (error) {
|
|
9111
|
+
this.dropFailure(inboxUrls, error);
|
|
9112
|
+
throw error;
|
|
9113
|
+
}
|
|
9114
|
+
}
|
|
9115
|
+
getContext() {
|
|
9116
|
+
return this.#currentContext ??= this.context();
|
|
9117
|
+
}
|
|
9118
|
+
useContext(context) {
|
|
9119
|
+
this.#currentContext = context;
|
|
9120
|
+
}
|
|
9121
|
+
setSpan(span, parentSpanContext) {
|
|
9122
|
+
this.#trace = snapshotTrace(span, parentSpanContext);
|
|
9123
|
+
}
|
|
9124
|
+
async prepare(phase, operation) {
|
|
9125
|
+
try {
|
|
9126
|
+
const result = await operation();
|
|
9127
|
+
this.#preparationFailure = void 0;
|
|
9128
|
+
return result;
|
|
9129
|
+
} catch (error) {
|
|
9130
|
+
this.#preparationFailure = Object.freeze({
|
|
9131
|
+
kind: "local",
|
|
9132
|
+
phase,
|
|
9133
|
+
error
|
|
9134
|
+
});
|
|
9135
|
+
throw error;
|
|
9136
|
+
}
|
|
9137
|
+
}
|
|
9138
|
+
recordPreparationFailure(error, inboxUrl) {
|
|
9139
|
+
const failure = this.#preparationFailure;
|
|
9140
|
+
if (failure == null || !Object.is(failure.error, error)) return [];
|
|
9141
|
+
const diagnostic = Object.freeze({
|
|
9142
|
+
inboxUrl,
|
|
9143
|
+
failure
|
|
9144
|
+
});
|
|
9145
|
+
this.#preparationFailures.push(diagnostic);
|
|
9146
|
+
return [diagnostic];
|
|
9147
|
+
}
|
|
9148
|
+
async run(operation) {
|
|
9149
|
+
try {
|
|
9150
|
+
await operation();
|
|
9151
|
+
} catch (error) {
|
|
9152
|
+
await this.#notify(this.#preparationFailure == null || !Object.is(this.#preparationFailure.error, error) ? {
|
|
9153
|
+
type: "failed",
|
|
9154
|
+
error
|
|
9155
|
+
} : {
|
|
9156
|
+
type: "preparation-failed",
|
|
9157
|
+
failure: this.#preparationFailure,
|
|
9158
|
+
error
|
|
9159
|
+
});
|
|
9160
|
+
throw error;
|
|
9161
|
+
}
|
|
9162
|
+
await this.#notify({ type: "completed" });
|
|
9163
|
+
}
|
|
9164
|
+
async #notify(result) {
|
|
9165
|
+
const observedAt = Temporal$5.Now.instant().epochMilliseconds;
|
|
9166
|
+
try {
|
|
9167
|
+
const context = this.getContext();
|
|
9168
|
+
await notifyObserver(context, this.callback, Object.freeze({
|
|
9169
|
+
role: this.#role,
|
|
9170
|
+
queue: this.#queue,
|
|
9171
|
+
...this.#delivery == null ? {} : { delivery: this.#delivery },
|
|
9172
|
+
attemptsStarted: this.#attemptsStarted,
|
|
9173
|
+
actions: Object.freeze([...this.#actions ?? []]),
|
|
9174
|
+
...this.#preparationFailures.length === 0 ? {} : { preparationFailures: Object.freeze([...this.#preparationFailures]) },
|
|
9175
|
+
observedAt,
|
|
9176
|
+
...this.#trace == null ? {} : { trace: this.#trace },
|
|
9177
|
+
provenance: this.provenance,
|
|
9178
|
+
local: snapshotLocalContext(context),
|
|
9179
|
+
result: Object.freeze(result)
|
|
9180
|
+
}), "worker");
|
|
9181
|
+
} catch (error) {
|
|
9182
|
+
logObserverFailure("worker", error);
|
|
9183
|
+
}
|
|
9184
|
+
}
|
|
9185
|
+
};
|
|
9186
|
+
/** Selected callback and restored grouping, shared by concrete gateway attempts. */
|
|
9187
|
+
var OutboundAttemptObserver = class OutboundAttemptObserver {
|
|
9188
|
+
context;
|
|
9189
|
+
callback;
|
|
9190
|
+
identity;
|
|
9191
|
+
provenance;
|
|
9192
|
+
onHttpExchange;
|
|
9193
|
+
headerPolicies;
|
|
9194
|
+
bodyCapture;
|
|
9195
|
+
#queue;
|
|
9196
|
+
constructor(context, callback, identity, provenance, queue, onHttpExchange = void 0, headerPolicies = {}, bodyCapture = void 0) {
|
|
9197
|
+
this.context = context;
|
|
9198
|
+
this.callback = callback;
|
|
9199
|
+
this.identity = identity;
|
|
9200
|
+
this.provenance = provenance;
|
|
9201
|
+
this.onHttpExchange = onHttpExchange;
|
|
9202
|
+
this.headerPolicies = headerPolicies;
|
|
9203
|
+
this.bodyCapture = bodyCapture;
|
|
9204
|
+
this.#queue = queue == null ? void 0 : Object.freeze({ ...queue });
|
|
9205
|
+
}
|
|
9206
|
+
static resumeOutbox(context, callback, message, provenance, onHttpExchange, headerPolicies = {}, bodyCapture) {
|
|
9207
|
+
if (callback == null && onHttpExchange == null && bodyCapture == null) return;
|
|
9208
|
+
const identity = snapshotOutboxIdentity(message);
|
|
9209
|
+
return new OutboundAttemptObserver(context, callback, identity, provenance, {
|
|
9210
|
+
queueMessageId: message.id,
|
|
9211
|
+
queueAttempt: message.attempt
|
|
9212
|
+
}, onHttpExchange, headerPolicies, bodyCapture);
|
|
9213
|
+
}
|
|
9214
|
+
startAttempt(inboxUrl) {
|
|
9215
|
+
if (this.callback == null && this.onHttpExchange == null && this.bodyCapture == null) return void 0;
|
|
9216
|
+
const context = this.context();
|
|
9217
|
+
return new OutboundAttemptObservation(inboxUrl, (facts) => notifyObserver(context, this.callback, Object.freeze({
|
|
9218
|
+
...this.identity,
|
|
9219
|
+
...facts,
|
|
9220
|
+
...this.#queue == null ? {} : { queue: this.#queue },
|
|
9221
|
+
provenance: this.provenance,
|
|
9222
|
+
local: snapshotLocalContext(context)
|
|
9223
|
+
}), "attempt"), (facts) => notifyObserver(context, this.onHttpExchange, Object.freeze({
|
|
9224
|
+
...this.identity,
|
|
9225
|
+
...facts,
|
|
9226
|
+
...this.#queue == null ? {} : { queue: this.#queue },
|
|
9227
|
+
provenance: this.provenance,
|
|
9228
|
+
local: snapshotLocalContext(context)
|
|
9229
|
+
}), "http-exchange"), this.onHttpExchange == null && this.bodyCapture == null ? {} : this.headerPolicies, this.bodyCapture == null ? void 0 : {
|
|
9230
|
+
...this.bodyCapture,
|
|
9231
|
+
onBody: (event) => (0, _logtape_logtape.withContext)(sourceLogContext(this.provenance), () => this.bodyCapture.onBody(context, Object.freeze({
|
|
9232
|
+
...this.identity,
|
|
9233
|
+
...event,
|
|
9234
|
+
...this.#queue == null ? {} : { queue: this.#queue },
|
|
9235
|
+
provenance: this.provenance,
|
|
9236
|
+
local: snapshotLocalContext(context)
|
|
9237
|
+
})))
|
|
9238
|
+
}, this.callback != null && this.headerPolicies.senderPayload === true);
|
|
9239
|
+
}
|
|
9240
|
+
};
|
|
9241
|
+
/** One producer's destination, without callbacks or live inputs in queues. */
|
|
9242
|
+
var OutboundDeliveryObservation = class {
|
|
9243
|
+
context;
|
|
9244
|
+
observer;
|
|
9245
|
+
provenance;
|
|
9246
|
+
trace;
|
|
9247
|
+
#finished = false;
|
|
9248
|
+
#reference;
|
|
9249
|
+
#plan;
|
|
9250
|
+
#attempts;
|
|
9251
|
+
constructor(context, observer, identity, destination, provenance, trace, onAttempt, onHttpExchange, queue, headerPolicies = {}, bodyCapture) {
|
|
9252
|
+
this.context = context;
|
|
9253
|
+
this.observer = observer;
|
|
9254
|
+
this.provenance = provenance;
|
|
9255
|
+
this.trace = trace;
|
|
9256
|
+
this.#reference = snapshotDeliveryIdentity(identity);
|
|
9257
|
+
this.#attempts = new OutboundAttemptObserver(() => this.context, onAttempt, this.#reference, this.provenance, queue, onHttpExchange, headerPolicies, bodyCapture);
|
|
9258
|
+
this.#plan = Object.freeze({
|
|
9259
|
+
actorIds: Object.freeze(Array.from(destination.actorIds)),
|
|
9260
|
+
sharedInbox: destination.sharedInbox,
|
|
9261
|
+
mode: destination.mode ?? (destination.portableInbox == null ? "ordinary" : "portable-fallback")
|
|
9262
|
+
});
|
|
9263
|
+
}
|
|
9264
|
+
startAttempt(inboxUrl) {
|
|
9265
|
+
return this.#attempts.startAttempt(inboxUrl);
|
|
9266
|
+
}
|
|
9267
|
+
get identity() {
|
|
9268
|
+
return this.#reference;
|
|
9269
|
+
}
|
|
9270
|
+
planned() {
|
|
9271
|
+
return notifyObserver(this.context, this.observer, Object.freeze({
|
|
9272
|
+
...this.trace == null ? {} : { trace: this.trace },
|
|
9273
|
+
observedAt: Temporal$5.Now.instant().epochMilliseconds,
|
|
9274
|
+
provenance: this.provenance,
|
|
9275
|
+
local: snapshotLocalContext(this.context),
|
|
9276
|
+
...this.#reference,
|
|
9277
|
+
...this.#plan,
|
|
9278
|
+
stage: "planned"
|
|
9279
|
+
}), "delivery");
|
|
9280
|
+
}
|
|
9281
|
+
handoff(result) {
|
|
9282
|
+
if (this.#finished) return Promise.resolve();
|
|
9283
|
+
this.#finished = true;
|
|
9284
|
+
return notifyObserver(this.context, this.observer, Object.freeze({
|
|
9285
|
+
...this.trace == null ? {} : { trace: this.trace },
|
|
9286
|
+
observedAt: Temporal$5.Now.instant().epochMilliseconds,
|
|
9287
|
+
provenance: this.provenance,
|
|
9288
|
+
local: snapshotLocalContext(this.context),
|
|
9289
|
+
...this.#reference,
|
|
9290
|
+
stage: "handoff",
|
|
9291
|
+
result: Object.freeze(result)
|
|
9292
|
+
}), "delivery");
|
|
9293
|
+
}
|
|
9294
|
+
failed(error) {
|
|
9295
|
+
if (this.#finished) return Promise.resolve();
|
|
9296
|
+
this.#finished = true;
|
|
9297
|
+
return notifyObserver(this.context, this.observer, Object.freeze({
|
|
9298
|
+
...this.trace == null ? {} : { trace: this.trace },
|
|
9299
|
+
observedAt: Temporal$5.Now.instant().epochMilliseconds,
|
|
9300
|
+
provenance: this.provenance,
|
|
9301
|
+
local: snapshotLocalContext(this.context),
|
|
9302
|
+
...this.#reference,
|
|
9303
|
+
stage: "preparation-failed",
|
|
9304
|
+
failure: Object.freeze({
|
|
9305
|
+
kind: "local",
|
|
9306
|
+
phase: "request-preparation",
|
|
9307
|
+
error
|
|
9308
|
+
})
|
|
9309
|
+
}), "delivery");
|
|
9310
|
+
}
|
|
9311
|
+
};
|
|
9312
|
+
/** State owned by one outgoing invocation, never serialized into queues. */
|
|
9313
|
+
var OutboundBroadcastObservation = class OutboundBroadcastObservation {
|
|
9314
|
+
context;
|
|
9315
|
+
operation;
|
|
9316
|
+
provenance;
|
|
9317
|
+
queue;
|
|
9318
|
+
#broadcast;
|
|
9319
|
+
#finished = false;
|
|
9320
|
+
#phase = "unknown";
|
|
9321
|
+
#planning = "not-started";
|
|
9322
|
+
#destinationCount = 0;
|
|
9323
|
+
#trace;
|
|
9324
|
+
#activity = Object.freeze({});
|
|
9325
|
+
#onBroadcast;
|
|
9326
|
+
#onDelivery;
|
|
9327
|
+
#onAttempt;
|
|
9328
|
+
#onHttpExchange;
|
|
9329
|
+
#headerPolicies;
|
|
9330
|
+
#bodyCapture;
|
|
9331
|
+
#deliveries = /* @__PURE__ */ new Map();
|
|
9332
|
+
constructor(context, observers, operation, provenance = getOutboundProvenance(context), broadcast = {
|
|
9333
|
+
type: "known",
|
|
9334
|
+
id: crypto.randomUUID()
|
|
9335
|
+
}, queue = void 0) {
|
|
9336
|
+
this.context = context;
|
|
9337
|
+
this.operation = operation;
|
|
9338
|
+
this.provenance = provenance;
|
|
9339
|
+
this.queue = queue;
|
|
9340
|
+
this.#broadcast = broadcast;
|
|
9341
|
+
this.#onBroadcast = observers.onBroadcast;
|
|
9342
|
+
this.#onDelivery = observers.onDelivery;
|
|
9343
|
+
this.#onAttempt = observers.onAttempt;
|
|
9344
|
+
this.#onHttpExchange = observers.onHttpExchange;
|
|
9345
|
+
this.#bodyCapture = observers.responseBodyCapture;
|
|
9346
|
+
this.#headerPolicies = {
|
|
9347
|
+
requestHeaders: observers.requestHeaders,
|
|
9348
|
+
responseHeaders: observers.responseHeaders,
|
|
9349
|
+
senderPayload: observers.senderPayload
|
|
9350
|
+
};
|
|
9351
|
+
}
|
|
9352
|
+
/** Continue an existing fanout; do not emit a new broadcast invocation. */
|
|
9353
|
+
static async resumeFanout(context, observers, message, span, parentSpanContext) {
|
|
9354
|
+
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;
|
|
9355
|
+
const observation = new OutboundBroadcastObservation(context, {
|
|
9356
|
+
onDelivery: observers.onDelivery,
|
|
9357
|
+
onAttempt: observers.onAttempt,
|
|
9358
|
+
onHttpExchange: observers.onHttpExchange,
|
|
9359
|
+
requestHeaders: observers.requestHeaders,
|
|
9360
|
+
responseHeaders: observers.responseHeaders,
|
|
9361
|
+
responseBodyCapture: observers.responseBodyCapture,
|
|
9362
|
+
senderPayload: observers.senderPayload
|
|
9363
|
+
}, "send", getOutboundProvenance(context), { type: "unknown" }, { queueMessageId: message.id });
|
|
9364
|
+
if (span != null) observation.setSpan(span, parentSpanContext);
|
|
9365
|
+
observation.#finished = true;
|
|
9366
|
+
observation.planned(Object.keys(message.inboxes).length);
|
|
9367
|
+
for (const [inbox, destination] of Object.entries(message.inboxes)) {
|
|
9368
|
+
let identity;
|
|
9369
|
+
if (destination.outbound != null) try {
|
|
9370
|
+
const snapshot = snapshotDeliveryIdentity(destination.outbound);
|
|
9371
|
+
if (snapshot.destinationInbox !== inbox) throw new TypeError("Outbound delivery identity does not match its fanout destination.");
|
|
9372
|
+
identity = snapshot;
|
|
9373
|
+
} catch (error) {
|
|
9374
|
+
warnInvalidDeliveryIdentity(message.id, error);
|
|
9375
|
+
}
|
|
9376
|
+
identity ??= await legacyFanoutIdentity(message.id, inbox, destination.portableInbox);
|
|
9377
|
+
observation.#deliveries.set(inbox, observation.#createDelivery(inbox, destination, identity));
|
|
9378
|
+
}
|
|
9379
|
+
return observation;
|
|
9380
|
+
}
|
|
9381
|
+
#createDelivery(inbox, destination, identity) {
|
|
9382
|
+
return new OutboundDeliveryObservation(this.context, this.#onDelivery, identity ?? {
|
|
9383
|
+
broadcast: this.#broadcast,
|
|
9384
|
+
deliveryId: crypto.randomUUID(),
|
|
9385
|
+
destinationInbox: inbox,
|
|
9386
|
+
...destination.portableInbox == null ? {} : { portableInbox: destination.portableInbox }
|
|
9387
|
+
}, destination, this.provenance, this.#trace, this.#onAttempt, this.#onHttpExchange, this.queue, this.#headerPolicies, this.#bodyCapture);
|
|
9388
|
+
}
|
|
9389
|
+
continuation(inbox) {
|
|
9390
|
+
const delivery = this.delivery(inbox);
|
|
9391
|
+
return delivery == null ? void 0 : { outbound: delivery.identity };
|
|
9392
|
+
}
|
|
9393
|
+
async planDestinations(inboxes) {
|
|
9394
|
+
this.planned(Object.keys(inboxes).length);
|
|
9395
|
+
for (const [inbox, destination] of Object.entries(inboxes)) if (!this.#deliveries.has(inbox)) this.#deliveries.set(inbox, this.#createDelivery(inbox, destination));
|
|
9396
|
+
for (const delivery of this.#deliveries.values()) await delivery.planned();
|
|
9397
|
+
}
|
|
9398
|
+
delivery(inbox) {
|
|
9399
|
+
return this.#deliveries.get(inbox);
|
|
9400
|
+
}
|
|
9401
|
+
async enqueue(entries, queue, enqueue) {
|
|
9402
|
+
let result;
|
|
9403
|
+
try {
|
|
9404
|
+
result = await enqueue();
|
|
9405
|
+
} catch (error) {
|
|
9406
|
+
await Promise.all(entries.map(({ inbox }) => this.delivery(inbox)?.handoff({
|
|
9407
|
+
type: "enqueue-failed",
|
|
9408
|
+
acceptance: "unknown",
|
|
9409
|
+
error
|
|
9410
|
+
})));
|
|
9411
|
+
throw error;
|
|
9412
|
+
}
|
|
9413
|
+
await Promise.all(entries.map(({ inbox, queueMessageId }) => this.delivery(inbox)?.handoff({
|
|
9414
|
+
type: "enqueued",
|
|
9415
|
+
queue,
|
|
9416
|
+
queueMessageId
|
|
9417
|
+
})));
|
|
9418
|
+
return result;
|
|
9419
|
+
}
|
|
9420
|
+
/** Attach only the span supplied by this execution, never an ambient fallback. */
|
|
9421
|
+
setSpan(span, parentSpanContext) {
|
|
9422
|
+
this.#trace = snapshotTrace(span, parentSpanContext);
|
|
9423
|
+
}
|
|
9424
|
+
setPhase(phase) {
|
|
9425
|
+
this.#phase = phase;
|
|
9426
|
+
if (phase === "destination-planning") this.#planning = "incomplete";
|
|
9427
|
+
}
|
|
9428
|
+
/** Replace the Activity facts captured by the producer path. */
|
|
9429
|
+
setActivity(reference) {
|
|
9430
|
+
this.#activity = Object.freeze({ ...reference });
|
|
9431
|
+
}
|
|
9432
|
+
planned(destinationCount) {
|
|
9433
|
+
this.#planning = "complete";
|
|
9434
|
+
this.#destinationCount = destinationCount;
|
|
9435
|
+
}
|
|
9436
|
+
async planDestination(inbox, destination) {
|
|
9437
|
+
this.#destinationCount++;
|
|
9438
|
+
const delivery = this.#createDelivery(inbox, destination);
|
|
9439
|
+
this.#deliveries.set(inbox, delivery);
|
|
9440
|
+
await delivery.planned();
|
|
9441
|
+
}
|
|
9442
|
+
prepared(preparedFor) {
|
|
9443
|
+
return this.#finish(Object.freeze({
|
|
9444
|
+
type: "prepared",
|
|
9445
|
+
preparedFor,
|
|
9446
|
+
destinationCount: this.#destinationCount
|
|
9447
|
+
}));
|
|
9448
|
+
}
|
|
9449
|
+
skipped(reason = "no-destinations") {
|
|
9450
|
+
return this.#finish(Object.freeze({
|
|
9451
|
+
type: "skipped",
|
|
9452
|
+
reason
|
|
9453
|
+
}));
|
|
9454
|
+
}
|
|
9455
|
+
async run(send) {
|
|
9456
|
+
if (this.#broadcast.type === "known") await this.#notify(Object.freeze({
|
|
9457
|
+
broadcastId: this.#broadcast.id,
|
|
9458
|
+
operation: this.operation,
|
|
9459
|
+
stage: "started",
|
|
9460
|
+
activity: this.#activity
|
|
9461
|
+
}));
|
|
9462
|
+
try {
|
|
9463
|
+
return await send(this, this.provenance);
|
|
9464
|
+
} catch (error) {
|
|
9465
|
+
await this.failed(error);
|
|
9466
|
+
throw error;
|
|
9467
|
+
}
|
|
9468
|
+
}
|
|
9469
|
+
failed(error) {
|
|
9470
|
+
const failure = {
|
|
9471
|
+
kind: this.#phase === "unknown" ? "unknown" : "local",
|
|
9472
|
+
phase: this.#phase,
|
|
9473
|
+
error
|
|
9474
|
+
};
|
|
9475
|
+
return this.#finish(Object.freeze({
|
|
9476
|
+
type: "failed",
|
|
9477
|
+
failure: Object.freeze(failure),
|
|
9478
|
+
planning: this.#planning,
|
|
9479
|
+
plannedDestinationCount: this.#destinationCount
|
|
9480
|
+
}));
|
|
9481
|
+
}
|
|
9482
|
+
async #finish(result) {
|
|
9483
|
+
if (this.#finished) return;
|
|
9484
|
+
this.#finished = true;
|
|
9485
|
+
if (this.#broadcast.type === "known") await this.#notify(Object.freeze({
|
|
9486
|
+
broadcastId: this.#broadcast.id,
|
|
9487
|
+
operation: this.operation,
|
|
9488
|
+
stage: "preparation-finished",
|
|
9489
|
+
result,
|
|
9490
|
+
activity: this.#activity
|
|
9491
|
+
}));
|
|
9492
|
+
}
|
|
9493
|
+
#notify(event) {
|
|
9494
|
+
return notifyObserver(this.context, this.#onBroadcast, Object.freeze({
|
|
9495
|
+
...event,
|
|
9496
|
+
...this.#trace == null ? {} : { trace: this.#trace },
|
|
9497
|
+
observedAt: Temporal$5.Now.instant().epochMilliseconds,
|
|
9498
|
+
provenance: this.provenance,
|
|
9499
|
+
local: snapshotLocalContext(this.context)
|
|
9500
|
+
}), "broadcast");
|
|
9501
|
+
}
|
|
9502
|
+
};
|
|
9503
|
+
/** Observe preparation without changing the unobserved send's execution path. */
|
|
9504
|
+
function observeOutboundBroadcast(context, observers, send, operation = "send", dispatchId) {
|
|
9505
|
+
let provenance;
|
|
9506
|
+
let invalid;
|
|
9507
|
+
try {
|
|
9508
|
+
provenance = invocationProvenance(context, dispatchId);
|
|
9509
|
+
} catch (error) {
|
|
9510
|
+
provenance = invocationProvenance(context, void 0);
|
|
9511
|
+
invalid = { error };
|
|
9512
|
+
}
|
|
9513
|
+
const execute = async (observation) => {
|
|
9514
|
+
if (invalid != null) {
|
|
9515
|
+
observation?.setPhase("validation");
|
|
9516
|
+
throw invalid.error;
|
|
9517
|
+
}
|
|
9518
|
+
return await send(observation, provenance);
|
|
9519
|
+
};
|
|
9520
|
+
return (0, _logtape_logtape.withContext)(sourceLogContext(provenance), () => {
|
|
9521
|
+
if (observers.onBroadcast == null && observers.onDelivery == null && observers.onAttempt == null && observers.onHttpExchange == null && observers.responseBodyCapture == null) return execute();
|
|
9522
|
+
return new OutboundBroadcastObservation(context, observers, operation, provenance).run(execute);
|
|
9523
|
+
});
|
|
9524
|
+
}
|
|
9525
|
+
//#endregion
|
|
7961
9526
|
//#region src/nodeinfo/handler.ts
|
|
7962
9527
|
/**
|
|
7963
9528
|
* Handles a NodeInfo request. You would not typically call this function
|
|
@@ -9541,41 +11106,70 @@ var FederationImpl = class extends FederationBuilderImpl {
|
|
|
9541
11106
|
}
|
|
9542
11107
|
await Promise.all(promises);
|
|
9543
11108
|
}
|
|
9544
|
-
processQueuedTask(contextData, message) {
|
|
11109
|
+
async processQueuedTask(contextData, message) {
|
|
9545
11110
|
const extractedContext = _opentelemetry_api.propagation.extract(_opentelemetry_api.context.active(), message.traceContext);
|
|
9546
|
-
|
|
11111
|
+
let provenance = emptyProvenance;
|
|
11112
|
+
if (message.type !== "task") try {
|
|
11113
|
+
provenance = restoreProvenance(message.provenance);
|
|
11114
|
+
} catch (error) {
|
|
11115
|
+
try {
|
|
11116
|
+
(0, _logtape_logtape.getLogger)([
|
|
11117
|
+
"fedify",
|
|
11118
|
+
"federation",
|
|
11119
|
+
"queue"
|
|
11120
|
+
]).warn("Ignoring invalid provenance in queue message {messageId}: {error}", {
|
|
11121
|
+
messageId: message.id,
|
|
11122
|
+
error
|
|
11123
|
+
});
|
|
11124
|
+
} catch {}
|
|
11125
|
+
}
|
|
11126
|
+
if (message.type !== "task" && message.provenance !== void 0) message = {
|
|
11127
|
+
...message,
|
|
11128
|
+
provenance
|
|
11129
|
+
};
|
|
11130
|
+
return await (0, _logtape_logtape.withContext)({
|
|
11131
|
+
messageId: message.id,
|
|
11132
|
+
inboxRequestId: void 0,
|
|
11133
|
+
...sourceLogContext(provenance)
|
|
11134
|
+
}, async () => {
|
|
9547
11135
|
if (message.type === "fanout") {
|
|
11136
|
+
const observers = this.outboundObservers;
|
|
11137
|
+
const worker = this.#createOutboundWorkerObservation(contextData, message, provenance, observers.onWorker);
|
|
9548
11138
|
const common = {
|
|
9549
11139
|
role: "fanout",
|
|
9550
11140
|
queue: this.fanoutQueue,
|
|
9551
11141
|
activityType: message.activityType
|
|
9552
11142
|
};
|
|
9553
|
-
|
|
11143
|
+
const execute = () => this.#runWorkerSpan("activitypub.fanout", { "activitypub.activity.type": message.activityType }, extractedContext, this.#instrumentWorkerBody(common, async (span) => {
|
|
9554
11144
|
if (message.activityId != null) span.setAttribute("activitypub.activity.id", message.activityId);
|
|
9555
|
-
await this.#listenFanoutMessage(contextData, message);
|
|
11145
|
+
await this.#listenFanoutMessage(contextData, message, provenance, span, _opentelemetry_api.trace.getSpanContext(extractedContext), observers, worker);
|
|
9556
11146
|
return { outcome: "completed" };
|
|
9557
|
-
}, classifyAbortableError));
|
|
11147
|
+
}, classifyAbortableError), worker);
|
|
11148
|
+
await (worker == null ? execute() : worker.run(execute));
|
|
9558
11149
|
} else if (message.type === "outbox") {
|
|
11150
|
+
const observers = this.outboundObservers;
|
|
11151
|
+
const worker = this.#createOutboundWorkerObservation(contextData, message, provenance, observers.onWorker);
|
|
9559
11152
|
const common = {
|
|
9560
11153
|
role: "outbox",
|
|
9561
11154
|
queue: this.outboxQueue,
|
|
9562
11155
|
activityType: message.activityType
|
|
9563
11156
|
};
|
|
9564
|
-
|
|
11157
|
+
const execute = () => this.#runWorkerSpan("activitypub.outbox", {
|
|
9565
11158
|
"activitypub.activity.type": message.activityType,
|
|
9566
11159
|
"activitypub.activity.retries": message.attempt
|
|
9567
11160
|
}, extractedContext, this.#instrumentWorkerBody(common, async (span) => {
|
|
9568
11161
|
if (message.activityId != null) span.setAttribute("activitypub.activity.id", message.activityId);
|
|
9569
|
-
await this.#listenOutboxMessage(contextData, message, span);
|
|
11162
|
+
await this.#listenOutboxMessage(contextData, message, span, provenance, observers, worker);
|
|
9570
11163
|
return { outcome: "completed" };
|
|
9571
|
-
}, classifyAbortableError));
|
|
11164
|
+
}, classifyAbortableError), worker);
|
|
11165
|
+
await (worker == null ? execute() : worker.run(execute));
|
|
9572
11166
|
} else if (message.type === "inbox") {
|
|
9573
11167
|
const common = {
|
|
9574
11168
|
role: "inbox",
|
|
9575
11169
|
queue: this.inboxQueue
|
|
9576
11170
|
};
|
|
9577
11171
|
await this.#runWorkerSpan("activitypub.inbox", { "activitypub.shared_inbox": message.identifier == null }, extractedContext, this.#instrumentWorkerBody(common, async (span) => {
|
|
9578
|
-
await this.#listenInboxMessage(contextData, message, span, (activityType) => {
|
|
11172
|
+
await this.#listenInboxMessage(contextData, message, span, provenance, (activityType) => {
|
|
9579
11173
|
common.activityType = activityType;
|
|
9580
11174
|
});
|
|
9581
11175
|
return { outcome: "completed" };
|
|
@@ -9594,6 +11188,22 @@ var FederationImpl = class extends FederationBuilderImpl {
|
|
|
9594
11188
|
}
|
|
9595
11189
|
});
|
|
9596
11190
|
}
|
|
11191
|
+
#createOutboundWorkerObservation(contextData, message, provenance, callback) {
|
|
11192
|
+
if (callback == null) return void 0;
|
|
11193
|
+
let documentLoader;
|
|
11194
|
+
let contextLoader;
|
|
11195
|
+
return new OutboundWorkerObservation(() => {
|
|
11196
|
+
const loaderOptions = this.#getLoaderOptions(message.baseUrl);
|
|
11197
|
+
return new ContextImpl({
|
|
11198
|
+
[outboundProvenance]: provenance,
|
|
11199
|
+
federation: this,
|
|
11200
|
+
url: new URL("/", message.baseUrl),
|
|
11201
|
+
data: contextData,
|
|
11202
|
+
documentLoader: (...args) => (documentLoader ??= this.documentLoaderFactory(loaderOptions))(...args),
|
|
11203
|
+
contextLoader: (...args) => (contextLoader ??= this.contextLoaderFactory(loaderOptions))(...args)
|
|
11204
|
+
});
|
|
11205
|
+
}, callback, message, provenance);
|
|
11206
|
+
}
|
|
9597
11207
|
/**
|
|
9598
11208
|
* Opens a CONSUMER span for a queue worker and scopes the extracted trace
|
|
9599
11209
|
* context around its body. The body—typically built by
|
|
@@ -9605,11 +11215,12 @@ var FederationImpl = class extends FederationBuilderImpl {
|
|
|
9605
11215
|
* @param instrumentedBody Builds the context-scoped worker callback for the
|
|
9606
11216
|
* opened span.
|
|
9607
11217
|
*/
|
|
9608
|
-
async #runWorkerSpan(spanName, spanAttributes, extractedContext, instrumentedBody) {
|
|
11218
|
+
async #runWorkerSpan(spanName, spanAttributes, extractedContext, instrumentedBody, worker) {
|
|
9609
11219
|
await this._getTracer().startActiveSpan(spanName, {
|
|
9610
11220
|
kind: _opentelemetry_api.SpanKind.CONSUMER,
|
|
9611
11221
|
attributes: spanAttributes
|
|
9612
11222
|
}, extractedContext, async (span) => {
|
|
11223
|
+
worker?.setSpan(span, _opentelemetry_api.trace.getSpanContext(extractedContext));
|
|
9613
11224
|
const spanCtx = span.spanContext();
|
|
9614
11225
|
return await (0, _logtape_logtape.withContext)({
|
|
9615
11226
|
traceId: spanCtx.traceId,
|
|
@@ -9652,7 +11263,8 @@ var FederationImpl = class extends FederationBuilderImpl {
|
|
|
9652
11263
|
span.end();
|
|
9653
11264
|
}
|
|
9654
11265
|
};
|
|
9655
|
-
async #listenFanoutMessage(data, message) {
|
|
11266
|
+
async #listenFanoutMessage(data, message, provenance, span, parentSpanContext, observers, worker) {
|
|
11267
|
+
worker?.enableActions();
|
|
9656
11268
|
(0, _logtape_logtape.getLogger)([
|
|
9657
11269
|
"fedify",
|
|
9658
11270
|
"federation",
|
|
@@ -9661,11 +11273,12 @@ var FederationImpl = class extends FederationBuilderImpl {
|
|
|
9661
11273
|
activityId: message.activityId,
|
|
9662
11274
|
inboxes: globalThis.Object.keys(message.inboxes).length
|
|
9663
11275
|
});
|
|
9664
|
-
const
|
|
11276
|
+
const importKeys = () => Promise.all(message.keys.map(async ({ keyId, privateKey }) => ({
|
|
9665
11277
|
keyId: new URL(keyId),
|
|
9666
11278
|
privateKey: await require_http.importJwk(privateKey, "private")
|
|
9667
11279
|
})));
|
|
9668
|
-
const
|
|
11280
|
+
const keys = worker == null ? await importKeys() : await worker.prepare("key-import", importKeys);
|
|
11281
|
+
const parseActivity = () => _fedify_vocab.Activity.fromJsonLd(message.activity, {
|
|
9669
11282
|
contextLoader: this.contextLoaderFactory({
|
|
9670
11283
|
allowPrivateAddress: this.allowPrivateAddress,
|
|
9671
11284
|
userAgent: this.userAgent
|
|
@@ -9676,19 +11289,30 @@ var FederationImpl = class extends FederationBuilderImpl {
|
|
|
9676
11289
|
}),
|
|
9677
11290
|
tracerProvider: this.tracerProvider
|
|
9678
11291
|
});
|
|
9679
|
-
const
|
|
9680
|
-
|
|
9681
|
-
|
|
9682
|
-
|
|
11292
|
+
const activity = worker == null ? await parseActivity() : await worker.prepare("activity-parsing", parseActivity);
|
|
11293
|
+
const createContext = () => this.#createContext(new URL(message.baseUrl), data, {
|
|
11294
|
+
[outboundProvenance]: provenance,
|
|
11295
|
+
documentLoader: this.documentLoaderFactory({
|
|
11296
|
+
allowPrivateAddress: this.allowPrivateAddress,
|
|
11297
|
+
userAgent: this.userAgent
|
|
11298
|
+
})
|
|
11299
|
+
});
|
|
11300
|
+
const context = worker == null ? createContext() : await worker.prepare("context-preparation", createContext);
|
|
11301
|
+
worker?.useContext(context);
|
|
11302
|
+
const observation = await OutboundBroadcastObservation.resumeFanout(context, observers, message, span, parentSpanContext);
|
|
9683
11303
|
await this.sendActivity(keys, message.inboxes, activity, {
|
|
9684
11304
|
collectionSync: message.collectionSync,
|
|
9685
11305
|
orderingKey: message.orderingKey,
|
|
9686
11306
|
normalizeExistingProofs: message.normalizeExistingProofs,
|
|
9687
11307
|
activityJsonLd: message.activity,
|
|
11308
|
+
observation,
|
|
11309
|
+
worker,
|
|
9688
11310
|
context
|
|
9689
11311
|
});
|
|
9690
11312
|
}
|
|
9691
|
-
async #listenOutboxMessage(
|
|
11313
|
+
async #listenOutboxMessage(contextData, message, span, provenance, observers, worker) {
|
|
11314
|
+
const { onAttempt, onHttpExchange } = observers;
|
|
11315
|
+
worker?.enableActions();
|
|
9692
11316
|
const logger = (0, _logtape_logtape.getLogger)([
|
|
9693
11317
|
"fedify",
|
|
9694
11318
|
"federation",
|
|
@@ -9705,14 +11329,26 @@ var FederationImpl = class extends FederationBuilderImpl {
|
|
|
9705
11329
|
const keys = [];
|
|
9706
11330
|
let rsaKeyPair = null;
|
|
9707
11331
|
for (const { keyId, privateKey } of message.keys) {
|
|
9708
|
-
const
|
|
11332
|
+
const importPair = async () => ({
|
|
9709
11333
|
keyId: new URL(keyId),
|
|
9710
11334
|
privateKey: await require_http.importJwk(privateKey, "private")
|
|
9711
|
-
};
|
|
11335
|
+
});
|
|
11336
|
+
const pair = worker == null ? await importPair() : await worker.prepare("key-import", importPair);
|
|
9712
11337
|
if (rsaKeyPair == null && pair.privateKey.algorithm.name === "RSASSA-PKCS1-v1_5") rsaKeyPair = pair;
|
|
9713
11338
|
keys.push(pair);
|
|
9714
11339
|
}
|
|
9715
11340
|
const loaderOptions = this.#getLoaderOptions(message.baseUrl);
|
|
11341
|
+
let observationContext;
|
|
11342
|
+
let observationDocumentLoader;
|
|
11343
|
+
let observationContextLoader;
|
|
11344
|
+
const observation = OutboundAttemptObserver.resumeOutbox(() => worker?.getContext() ?? (observationContext ??= new ContextImpl({
|
|
11345
|
+
[outboundProvenance]: provenance,
|
|
11346
|
+
federation: this,
|
|
11347
|
+
url: new URL("/", message.baseUrl),
|
|
11348
|
+
data: contextData,
|
|
11349
|
+
documentLoader: (...args) => (observationDocumentLoader ??= this.documentLoaderFactory(loaderOptions))(...args),
|
|
11350
|
+
contextLoader: (...args) => (observationContextLoader ??= this.contextLoaderFactory(loaderOptions))(...args)
|
|
11351
|
+
})), onAttempt, message, provenance, onHttpExchange, observers, observers.responseBodyCapture);
|
|
9716
11352
|
let parsedActorIds;
|
|
9717
11353
|
const getActorIds = () => {
|
|
9718
11354
|
parsedActorIds ??= (message.actorIds ?? []).flatMap((id) => {
|
|
@@ -9735,7 +11371,7 @@ var FederationImpl = class extends FederationBuilderImpl {
|
|
|
9735
11371
|
verifyPortableObject: createPortableObjectVerifier(documentLoader, contextLoader, this)
|
|
9736
11372
|
});
|
|
9737
11373
|
};
|
|
9738
|
-
const enqueueHeldOutboxMessage = async (delay, heldSince) => {
|
|
11374
|
+
const enqueueHeldOutboxMessage = async (delay, heldSince, preparationFailures) => {
|
|
9739
11375
|
const { outboxQueue } = this;
|
|
9740
11376
|
if (outboxQueue == null) return;
|
|
9741
11377
|
const heldMessage = {
|
|
@@ -9743,10 +11379,11 @@ var FederationImpl = class extends FederationBuilderImpl {
|
|
|
9743
11379
|
circuitHeld: true,
|
|
9744
11380
|
circuitHeldSince: heldSince.toString()
|
|
9745
11381
|
};
|
|
9746
|
-
|
|
11382
|
+
const enqueueOptions = {
|
|
9747
11383
|
delay: clampNegativeDelay(delay),
|
|
9748
11384
|
orderingKey: message.orderingKey
|
|
9749
|
-
}
|
|
11385
|
+
};
|
|
11386
|
+
await (worker == null ? outboxQueue.enqueue(heldMessage, enqueueOptions) : worker.enqueue("defer", heldMessage, enqueueOptions.delay, () => outboxQueue.enqueue(heldMessage, enqueueOptions), preparationFailures));
|
|
9750
11387
|
this.metrics.recordQueueTaskEnqueued({
|
|
9751
11388
|
role: "outbox",
|
|
9752
11389
|
queue: outboxQueue,
|
|
@@ -9763,7 +11400,10 @@ var FederationImpl = class extends FederationBuilderImpl {
|
|
|
9763
11400
|
heldSince
|
|
9764
11401
|
});
|
|
9765
11402
|
if (this.outboxPermanentFailureHandler != null) {
|
|
9766
|
-
const ctx = this.#createContext(new URL(message.baseUrl),
|
|
11403
|
+
const ctx = this.#createContext(new URL(message.baseUrl), contextData, {
|
|
11404
|
+
[outboundProvenance]: provenance,
|
|
11405
|
+
documentLoader: this.documentLoaderFactory(loaderOptions)
|
|
11406
|
+
});
|
|
9767
11407
|
try {
|
|
9768
11408
|
await this.outboxPermanentFailureHandler(ctx, {
|
|
9769
11409
|
reason: "circuit-breaker-ttl",
|
|
@@ -9775,6 +11415,7 @@ var FederationImpl = class extends FederationBuilderImpl {
|
|
|
9775
11415
|
actorIds: getActorIds()
|
|
9776
11416
|
});
|
|
9777
11417
|
} catch (handlerError) {
|
|
11418
|
+
worker?.dropFailure([inbox.href], handlerError);
|
|
9778
11419
|
logger.error("An unexpected error occurred in outboxPermanentFailureHandler:\n{error}", {
|
|
9779
11420
|
...logData,
|
|
9780
11421
|
error: handlerError
|
|
@@ -9785,10 +11426,13 @@ var FederationImpl = class extends FederationBuilderImpl {
|
|
|
9785
11426
|
};
|
|
9786
11427
|
const { gatewayInboxes } = message;
|
|
9787
11428
|
if (gatewayInboxes != null && gatewayInboxes.length > 1) {
|
|
9788
|
-
await this.#listenGatewayOutboxMessage(
|
|
11429
|
+
await this.#listenGatewayOutboxMessage(contextData, {
|
|
9789
11430
|
...message,
|
|
9790
11431
|
gatewayInboxes
|
|
9791
11432
|
}, span, {
|
|
11433
|
+
observation,
|
|
11434
|
+
worker,
|
|
11435
|
+
provenance,
|
|
9792
11436
|
keys,
|
|
9793
11437
|
logData,
|
|
9794
11438
|
getActorIds,
|
|
@@ -9798,7 +11442,8 @@ var FederationImpl = class extends FederationBuilderImpl {
|
|
|
9798
11442
|
return;
|
|
9799
11443
|
}
|
|
9800
11444
|
try {
|
|
9801
|
-
const
|
|
11445
|
+
const parseInbox = () => new URL(message.inbox);
|
|
11446
|
+
const inbox = worker == null ? parseInbox() : await worker.prepare("validation", parseInbox);
|
|
9802
11447
|
const circuit = this.outboxQueue == null ? void 0 : this.circuitBreaker;
|
|
9803
11448
|
const remoteHost = require_http.getRemoteHost(inbox);
|
|
9804
11449
|
let decision;
|
|
@@ -9822,13 +11467,19 @@ var FederationImpl = class extends FederationBuilderImpl {
|
|
|
9822
11467
|
return;
|
|
9823
11468
|
}
|
|
9824
11469
|
if (decision.type === "drop") {
|
|
9825
|
-
|
|
9826
|
-
|
|
11470
|
+
worker?.abandon("held-ttl-expired", [inbox.href]);
|
|
11471
|
+
const drop = async () => {
|
|
11472
|
+
const activity = await parseActivity();
|
|
11473
|
+
await dropHeldOutboxMessage(circuit, remoteHost, inbox, decision.heldSince, activity);
|
|
11474
|
+
};
|
|
11475
|
+
await (worker == null ? drop() : worker.drop([inbox.href], drop));
|
|
9827
11476
|
return;
|
|
9828
11477
|
}
|
|
9829
11478
|
if (decision.stateChange != null) recordCircuitBreakerSpanEvent(span, remoteHost, decision.stateChange);
|
|
9830
11479
|
}
|
|
9831
11480
|
await sendActivity({
|
|
11481
|
+
attemptStarted: worker?.attemptStarted,
|
|
11482
|
+
observation,
|
|
9832
11483
|
allowPrivateAddress: this.allowPrivateAddress,
|
|
9833
11484
|
keys,
|
|
9834
11485
|
activity: message.activity,
|
|
@@ -9836,7 +11487,7 @@ var FederationImpl = class extends FederationBuilderImpl {
|
|
|
9836
11487
|
activityType: message.activityType,
|
|
9837
11488
|
inbox,
|
|
9838
11489
|
sharedInbox: message.sharedInbox,
|
|
9839
|
-
headers: new Headers(message.headers),
|
|
11490
|
+
headers: worker == null ? new Headers(message.headers) : await worker.prepare("request-preparation", () => new Headers(message.headers)),
|
|
9840
11491
|
specDeterminer: new KvSpecDeterminer(this.kv, this.kvPrefixes.httpMessageSignaturesSpec, this.firstKnock, { specTtl: this.httpMessageSignaturesSpecTtl }),
|
|
9841
11492
|
meterProvider: this.meterProvider,
|
|
9842
11493
|
tracerProvider: this.tracerProvider
|
|
@@ -9856,6 +11507,7 @@ var FederationImpl = class extends FederationBuilderImpl {
|
|
|
9856
11507
|
});
|
|
9857
11508
|
}
|
|
9858
11509
|
} catch (error) {
|
|
11510
|
+
const preparationFailures = worker?.recordPreparationFailure(error, message.inbox) ?? [];
|
|
9859
11511
|
span.setStatus({
|
|
9860
11512
|
code: _opentelemetry_api.SpanStatusCode.ERROR,
|
|
9861
11513
|
message: String(error)
|
|
@@ -9864,7 +11516,7 @@ var FederationImpl = class extends FederationBuilderImpl {
|
|
|
9864
11516
|
if (error instanceof SendActivityError) return require_http.getRemoteHost(error.inbox);
|
|
9865
11517
|
try {
|
|
9866
11518
|
return require_http.getRemoteHost(new URL(message.inbox));
|
|
9867
|
-
} catch
|
|
11519
|
+
} catch {
|
|
9868
11520
|
logger.warn("Invalid inbox URL in queued outbox message: {inbox}", logData);
|
|
9869
11521
|
return;
|
|
9870
11522
|
}
|
|
@@ -9937,32 +11589,42 @@ var FederationImpl = class extends FederationBuilderImpl {
|
|
|
9937
11589
|
});
|
|
9938
11590
|
}
|
|
9939
11591
|
if (circuitDrop != null) {
|
|
9940
|
-
|
|
11592
|
+
worker?.abandon("held-ttl-expired", [circuitDrop.inbox.href], preparationFailures);
|
|
11593
|
+
const drop = () => dropHeldOutboxMessage(circuitDrop.circuit, circuitDrop.remoteHost, circuitDrop.inbox, circuitDrop.heldSince, activity);
|
|
11594
|
+
await (worker == null ? drop() : worker.drop([circuitDrop.inbox.href], drop));
|
|
9941
11595
|
return;
|
|
9942
11596
|
}
|
|
9943
11597
|
if (isPermanentFailure) {
|
|
11598
|
+
worker?.abandon("permanent-failure", [message.inbox], preparationFailures);
|
|
9944
11599
|
this.metrics.recordPermanentFailure(error.inbox, error.statusCode);
|
|
9945
11600
|
logger.warn("Permanent delivery failure for activity {activityId} to {inbox} ({status}); not retrying.", {
|
|
9946
11601
|
...logData,
|
|
9947
11602
|
status: error.statusCode
|
|
9948
11603
|
});
|
|
9949
11604
|
if (this.outboxPermanentFailureHandler != null) {
|
|
9950
|
-
const
|
|
9951
|
-
|
|
9952
|
-
|
|
9953
|
-
|
|
9954
|
-
inbox: new URL(message.inbox),
|
|
9955
|
-
activity,
|
|
9956
|
-
error,
|
|
9957
|
-
statusCode: error.statusCode,
|
|
9958
|
-
actorIds: getActorIds()
|
|
11605
|
+
const drop = async () => {
|
|
11606
|
+
const ctx = this.#createContext(new URL(message.baseUrl), contextData, {
|
|
11607
|
+
[outboundProvenance]: provenance,
|
|
11608
|
+
documentLoader: this.documentLoaderFactory(loaderOptions)
|
|
9959
11609
|
});
|
|
9960
|
-
|
|
9961
|
-
|
|
9962
|
-
|
|
9963
|
-
|
|
9964
|
-
|
|
9965
|
-
|
|
11610
|
+
try {
|
|
11611
|
+
await this.outboxPermanentFailureHandler(ctx, {
|
|
11612
|
+
reason: "http",
|
|
11613
|
+
inbox: new URL(message.inbox),
|
|
11614
|
+
activity,
|
|
11615
|
+
error,
|
|
11616
|
+
statusCode: error.statusCode,
|
|
11617
|
+
actorIds: getActorIds()
|
|
11618
|
+
});
|
|
11619
|
+
} catch (handlerError) {
|
|
11620
|
+
worker?.dropFailure([message.inbox], handlerError);
|
|
11621
|
+
logger.error("An unexpected error occurred in outboxPermanentFailureHandler:\n{error}", {
|
|
11622
|
+
...logData,
|
|
11623
|
+
error: handlerError
|
|
11624
|
+
});
|
|
11625
|
+
}
|
|
11626
|
+
};
|
|
11627
|
+
await (worker == null ? drop() : worker.drop([message.inbox], drop));
|
|
9966
11628
|
}
|
|
9967
11629
|
require_http.recordOutboxActivity(this.meterProvider, "abandoned", message.activityType);
|
|
9968
11630
|
return;
|
|
@@ -9974,7 +11636,7 @@ var FederationImpl = class extends FederationBuilderImpl {
|
|
|
9974
11636
|
});
|
|
9975
11637
|
recordCircuitBreakerHeldSpanEvent(span, circuitHold.remoteHost, circuitHold.state);
|
|
9976
11638
|
const circuit = this.circuitBreaker;
|
|
9977
|
-
await enqueueHeldOutboxMessage(retryAfterDelay == null || circuit == null ? circuitHold.delay : circuit.capHeldDelay(circuitHold.heldSince, maxDelay(circuitHold.delay, retryAfterDelay)), circuitHold.heldSince);
|
|
11639
|
+
await enqueueHeldOutboxMessage(retryAfterDelay == null || circuit == null ? circuitHold.delay : circuit.capHeldDelay(circuitHold.heldSince, maxDelay(circuitHold.delay, retryAfterDelay)), circuitHold.heldSince, preparationFailures);
|
|
9978
11640
|
return;
|
|
9979
11641
|
}
|
|
9980
11642
|
if (this.outboxQueue?.nativeRetrial && retryAfterDelay == null) {
|
|
@@ -9997,18 +11659,20 @@ var FederationImpl = class extends FederationBuilderImpl {
|
|
|
9997
11659
|
};
|
|
9998
11660
|
const { outboxQueue } = this;
|
|
9999
11661
|
if (outboxQueue != null) {
|
|
10000
|
-
|
|
11662
|
+
const enqueueOptions = {
|
|
10001
11663
|
delay: clampNegativeDelay(delay),
|
|
10002
11664
|
orderingKey: message.orderingKey
|
|
10003
|
-
}
|
|
11665
|
+
};
|
|
11666
|
+
await (worker == null ? outboxQueue.enqueue(retryMessage, enqueueOptions) : worker.enqueue("retry", retryMessage, enqueueOptions.delay, () => outboxQueue.enqueue(retryMessage, enqueueOptions), preparationFailures));
|
|
10004
11667
|
this.metrics.recordQueueTaskEnqueued({
|
|
10005
11668
|
role: "outbox",
|
|
10006
11669
|
queue: outboxQueue,
|
|
10007
11670
|
activityType: retryMessage.activityType
|
|
10008
11671
|
}, retryMessage.attempt);
|
|
10009
11672
|
require_http.recordOutboxActivity(this.meterProvider, "retried", retryMessage.activityType);
|
|
10010
|
-
}
|
|
11673
|
+
} else worker?.abandon("queue-unavailable", [message.inbox], preparationFailures);
|
|
10011
11674
|
} else {
|
|
11675
|
+
worker?.abandon("retry-policy-declined", [message.inbox], preparationFailures);
|
|
10012
11676
|
logger.error("Failed to send activity {activityId} to {inbox} after {attempt} attempts; giving up:\n{error}", {
|
|
10013
11677
|
...logData,
|
|
10014
11678
|
error
|
|
@@ -10043,25 +11707,32 @@ var FederationImpl = class extends FederationBuilderImpl {
|
|
|
10043
11707
|
"federation",
|
|
10044
11708
|
"circuit"
|
|
10045
11709
|
]);
|
|
10046
|
-
const { keys, logData, getActorIds, parseActivity, dropHeldOutboxMessage } = helpers;
|
|
11710
|
+
const { observation, worker, provenance, keys, logData, getActorIds, parseActivity, dropHeldOutboxMessage } = helpers;
|
|
10047
11711
|
const circuit = this.outboxQueue == null ? void 0 : this.circuitBreaker;
|
|
10048
11712
|
const specDeterminer = new KvSpecDeterminer(this.kv, this.kvPrefixes.httpMessageSignaturesSpec, this.firstKnock, { specTtl: this.httpMessageSignaturesSpecTtl });
|
|
10049
11713
|
const survivors = [];
|
|
11714
|
+
const preparationFailuresByRoute = /* @__PURE__ */ new Map();
|
|
11715
|
+
const permanentRoutes = /* @__PURE__ */ new Set();
|
|
10050
11716
|
let lastError;
|
|
10051
11717
|
let lastPermanent;
|
|
10052
11718
|
let activity;
|
|
10053
11719
|
const getActivity = async () => activity ??= await parseActivity();
|
|
10054
11720
|
const drop = async (circuit, remoteHost, inbox, heldSince) => {
|
|
10055
|
-
|
|
11721
|
+
const routes = message.gatewayInboxes.filter((route) => !permanentRoutes.has(route));
|
|
11722
|
+
worker?.abandon("held-ttl-expired", routes, routes.flatMap((route) => preparationFailuresByRoute.get(route) ?? []));
|
|
11723
|
+
const execute = async () => dropHeldOutboxMessage(circuit, remoteHost, inbox, heldSince, await getActivity());
|
|
11724
|
+
await (worker == null ? execute() : worker.drop(routes, execute));
|
|
10056
11725
|
};
|
|
10057
11726
|
const heldSince = parseInstant(message.circuitHeldSince);
|
|
10058
11727
|
if (circuit != null && heldSince != null && Temporal$2.Instant.compare(heldSince.add(circuit.options.heldActivityTtl), Temporal$2.Now.instant()) <= 0) {
|
|
10059
|
-
const
|
|
11728
|
+
const parseInbox = () => new URL(message.gatewayInboxes[0]);
|
|
11729
|
+
const inbox = worker == null ? parseInbox() : await worker.prepare("validation", parseInbox);
|
|
10060
11730
|
await drop(circuit, require_http.getRemoteHost(inbox), inbox, heldSince);
|
|
10061
11731
|
return;
|
|
10062
11732
|
}
|
|
10063
11733
|
for (const inboxHref of message.gatewayInboxes) {
|
|
10064
|
-
const
|
|
11734
|
+
const parseInbox = () => new URL(inboxHref);
|
|
11735
|
+
const inbox = worker == null ? parseInbox() : await worker.prepare("validation", parseInbox);
|
|
10065
11736
|
const remoteHost = require_http.getRemoteHost(inbox);
|
|
10066
11737
|
const now = Temporal$2.Now.instant();
|
|
10067
11738
|
const notBefore = parseInstant(message.gatewayNotBefore?.[inboxHref]);
|
|
@@ -10099,6 +11770,8 @@ var FederationImpl = class extends FederationBuilderImpl {
|
|
|
10099
11770
|
}
|
|
10100
11771
|
try {
|
|
10101
11772
|
await sendActivity({
|
|
11773
|
+
attemptStarted: worker?.attemptStarted,
|
|
11774
|
+
observation,
|
|
10102
11775
|
allowPrivateAddress: this.allowPrivateAddress,
|
|
10103
11776
|
keys,
|
|
10104
11777
|
activity: message.activity,
|
|
@@ -10106,12 +11779,14 @@ var FederationImpl = class extends FederationBuilderImpl {
|
|
|
10106
11779
|
activityType: message.activityType,
|
|
10107
11780
|
inbox,
|
|
10108
11781
|
sharedInbox: message.sharedInbox,
|
|
10109
|
-
headers: new Headers(message.headers),
|
|
11782
|
+
headers: worker == null ? new Headers(message.headers) : await worker.prepare("request-preparation", () => new Headers(message.headers)),
|
|
10110
11783
|
specDeterminer,
|
|
10111
11784
|
meterProvider: this.meterProvider,
|
|
10112
11785
|
tracerProvider: this.tracerProvider
|
|
10113
11786
|
});
|
|
10114
11787
|
} catch (error) {
|
|
11788
|
+
const preparationFailures = worker?.recordPreparationFailure(error, inboxHref) ?? [];
|
|
11789
|
+
if (preparationFailures.length > 0) preparationFailuresByRoute.set(inboxHref, preparationFailures);
|
|
10115
11790
|
lastError = error;
|
|
10116
11791
|
const isPermanentFailure = error instanceof SendActivityError && this.permanentFailureStatusCodes.includes(error.statusCode);
|
|
10117
11792
|
let retryAfter = !isPermanentFailure && error instanceof SendActivityError && (error.statusCode === 429 || error.statusCode === 503) ? parseRetryAfter(error.responseHeaders) : void 0;
|
|
@@ -10156,6 +11831,8 @@ var FederationImpl = class extends FederationBuilderImpl {
|
|
|
10156
11831
|
});
|
|
10157
11832
|
}
|
|
10158
11833
|
if (isPermanentFailure) {
|
|
11834
|
+
permanentRoutes.add(inboxHref);
|
|
11835
|
+
worker?.abandon("permanent-failure", [inboxHref], preparationFailures);
|
|
10159
11836
|
logger.warn("Permanent delivery failure for activity {activityId} to {inbox} ({status}); not trying the gateway again.", {
|
|
10160
11837
|
...logData,
|
|
10161
11838
|
inbox: inboxHref,
|
|
@@ -10220,22 +11897,29 @@ var FederationImpl = class extends FederationBuilderImpl {
|
|
|
10220
11897
|
portableInbox: message.portableInbox
|
|
10221
11898
|
});
|
|
10222
11899
|
if (this.outboxPermanentFailureHandler != null) {
|
|
10223
|
-
const
|
|
10224
|
-
|
|
10225
|
-
|
|
10226
|
-
|
|
10227
|
-
inbox,
|
|
10228
|
-
activity: await getActivity(),
|
|
10229
|
-
error,
|
|
10230
|
-
statusCode: error.statusCode,
|
|
10231
|
-
actorIds: getActorIds()
|
|
10232
|
-
});
|
|
10233
|
-
} catch (handlerError) {
|
|
10234
|
-
logger.error("An unexpected error occurred in outboxPermanentFailureHandler:\n{error}", {
|
|
10235
|
-
...logData,
|
|
10236
|
-
error: handlerError
|
|
11900
|
+
const drop = async () => {
|
|
11901
|
+
const ctx = this.#createContext(new URL(message.baseUrl), contextData, {
|
|
11902
|
+
[outboundProvenance]: provenance,
|
|
11903
|
+
documentLoader: this.documentLoaderFactory(this.#getLoaderOptions(message.baseUrl))
|
|
10237
11904
|
});
|
|
10238
|
-
|
|
11905
|
+
try {
|
|
11906
|
+
await this.outboxPermanentFailureHandler(ctx, {
|
|
11907
|
+
reason: "http",
|
|
11908
|
+
inbox,
|
|
11909
|
+
activity: await getActivity(),
|
|
11910
|
+
error,
|
|
11911
|
+
statusCode: error.statusCode,
|
|
11912
|
+
actorIds: getActorIds()
|
|
11913
|
+
});
|
|
11914
|
+
} catch (handlerError) {
|
|
11915
|
+
worker?.dropFailure(message.gatewayInboxes, handlerError);
|
|
11916
|
+
logger.error("An unexpected error occurred in outboxPermanentFailureHandler:\n{error}", {
|
|
11917
|
+
...logData,
|
|
11918
|
+
error: handlerError
|
|
11919
|
+
});
|
|
11920
|
+
}
|
|
11921
|
+
};
|
|
11922
|
+
await (worker == null ? drop() : worker.drop(message.gatewayInboxes, drop));
|
|
10239
11923
|
}
|
|
10240
11924
|
require_http.recordOutboxActivity(this.meterProvider, "abandoned", message.activityType);
|
|
10241
11925
|
return;
|
|
@@ -10271,10 +11955,11 @@ var FederationImpl = class extends FederationBuilderImpl {
|
|
|
10271
11955
|
...logData,
|
|
10272
11956
|
portableInbox: message.portableInbox
|
|
10273
11957
|
});
|
|
10274
|
-
|
|
11958
|
+
const enqueueOptions = {
|
|
10275
11959
|
delay: clampNegativeDelay(delay),
|
|
10276
11960
|
orderingKey: message.orderingKey
|
|
10277
|
-
}
|
|
11961
|
+
};
|
|
11962
|
+
await (worker == null ? outboxQueue.enqueue(heldMessage, enqueueOptions) : worker.enqueue("defer", heldMessage, enqueueOptions.delay, () => outboxQueue.enqueue(heldMessage, enqueueOptions), survivors.flatMap((route) => preparationFailuresByRoute.get(route.inbox) ?? [])));
|
|
10278
11963
|
this.metrics.recordQueueTaskEnqueued({
|
|
10279
11964
|
role: "outbox",
|
|
10280
11965
|
queue: outboxQueue,
|
|
@@ -10292,6 +11977,7 @@ var FederationImpl = class extends FederationBuilderImpl {
|
|
|
10292
11977
|
throw lastError;
|
|
10293
11978
|
}
|
|
10294
11979
|
if (policyDelay == null || outboxQueue == null) {
|
|
11980
|
+
worker?.abandon(outboxQueue == null ? "queue-unavailable" : "retry-policy-declined", survivors.map((route) => route.inbox), survivors.flatMap((route) => preparationFailuresByRoute.get(route.inbox) ?? []));
|
|
10295
11981
|
logger.error("Failed to send activity {activityId} to every gateway of {portableInbox} after {attempt} attempts; giving up:\n{error}", {
|
|
10296
11982
|
...logData,
|
|
10297
11983
|
portableInbox: message.portableInbox,
|
|
@@ -10310,10 +11996,11 @@ var FederationImpl = class extends FederationBuilderImpl {
|
|
|
10310
11996
|
portableInbox: message.portableInbox,
|
|
10311
11997
|
error: lastError
|
|
10312
11998
|
});
|
|
10313
|
-
|
|
11999
|
+
const enqueueOptions = {
|
|
10314
12000
|
delay: clampNegativeDelay(delay),
|
|
10315
12001
|
orderingKey: message.orderingKey
|
|
10316
|
-
}
|
|
12002
|
+
};
|
|
12003
|
+
await (worker == null ? outboxQueue.enqueue(retryMessage, enqueueOptions) : worker.enqueue("retry", retryMessage, enqueueOptions.delay, () => outboxQueue.enqueue(retryMessage, enqueueOptions), survivors.flatMap((route) => preparationFailuresByRoute.get(route.inbox) ?? [])));
|
|
10317
12004
|
this.metrics.recordQueueTaskEnqueued({
|
|
10318
12005
|
role: "outbox",
|
|
10319
12006
|
queue: outboxQueue,
|
|
@@ -10321,18 +12008,24 @@ var FederationImpl = class extends FederationBuilderImpl {
|
|
|
10321
12008
|
}, retryMessage.attempt);
|
|
10322
12009
|
require_http.recordOutboxActivity(this.meterProvider, "retried", retryMessage.activityType);
|
|
10323
12010
|
}
|
|
10324
|
-
async #listenInboxMessage(ctxData, message, span, onActivityType) {
|
|
12011
|
+
async #listenInboxMessage(ctxData, message, span, provenance, onActivityType) {
|
|
10325
12012
|
const logger = (0, _logtape_logtape.getLogger)([
|
|
10326
12013
|
"fedify",
|
|
10327
12014
|
"federation",
|
|
10328
12015
|
"inbox"
|
|
10329
12016
|
]);
|
|
10330
12017
|
const baseUrl = new URL(message.baseUrl);
|
|
10331
|
-
let context = this.#createContext(baseUrl, ctxData);
|
|
10332
|
-
if (message.identifier != null && this.actorCallbacks?.keyPairsDispatcher != null) context = this.#createContext(baseUrl, ctxData, {
|
|
12018
|
+
let context = this.#createContext(baseUrl, ctxData, { [outboundProvenance]: provenance });
|
|
12019
|
+
if (message.identifier != null && this.actorCallbacks?.keyPairsDispatcher != null) context = this.#createContext(baseUrl, ctxData, {
|
|
12020
|
+
[outboundProvenance]: provenance,
|
|
12021
|
+
documentLoader: await context.getDocumentLoader({ identifier: message.identifier })
|
|
12022
|
+
});
|
|
10333
12023
|
else if (message.identifier == null && this.sharedInboxKeyDispatcher != null) {
|
|
10334
12024
|
const identity = await this.sharedInboxKeyDispatcher(context);
|
|
10335
|
-
if (identity != null) context = this.#createContext(baseUrl, ctxData, {
|
|
12025
|
+
if (identity != null) context = this.#createContext(baseUrl, ctxData, {
|
|
12026
|
+
[outboundProvenance]: provenance,
|
|
12027
|
+
documentLoader: "identifier" in identity || "username" in identity ? await context.getDocumentLoader(identity) : context.getDocumentLoader(identity)
|
|
12028
|
+
});
|
|
10336
12029
|
}
|
|
10337
12030
|
await this._getTracer().startActiveSpan("activitypub.dispatch_inbox_listener", { kind: _opentelemetry_api.SpanKind.INTERNAL }, async (listenerSpan) => {
|
|
10338
12031
|
let activity = null;
|
|
@@ -10755,6 +12448,7 @@ var FederationImpl = class extends FederationBuilderImpl {
|
|
|
10755
12448
|
}
|
|
10756
12449
|
const loaderOptions = this.#getLoaderOptions(url.origin);
|
|
10757
12450
|
const ctxOptions = {
|
|
12451
|
+
[outboundProvenance]: opts[outboundProvenance],
|
|
10758
12452
|
url,
|
|
10759
12453
|
federation: this,
|
|
10760
12454
|
data: contextData,
|
|
@@ -10786,22 +12480,37 @@ var FederationImpl = class extends FederationBuilderImpl {
|
|
|
10786
12480
|
"federation",
|
|
10787
12481
|
"outbox"
|
|
10788
12482
|
]);
|
|
10789
|
-
const { immediate, collectionSync, orderingKey, context: ctx } = options;
|
|
10790
|
-
|
|
10791
|
-
|
|
10792
|
-
|
|
10793
|
-
|
|
10794
|
-
|
|
10795
|
-
|
|
10796
|
-
|
|
10797
|
-
|
|
10798
|
-
|
|
10799
|
-
|
|
10800
|
-
|
|
10801
|
-
|
|
12483
|
+
const { immediate, collectionSync, orderingKey, context: ctx, observation, worker, provenance = getOutboundProvenance(ctx) } = options;
|
|
12484
|
+
observation?.setPhase("validation");
|
|
12485
|
+
const validateActivity = () => {
|
|
12486
|
+
if (activity.id == null) throw new TypeError("The activity to send must have an id.");
|
|
12487
|
+
if (activity.actorId == null) throw new TypeError("The activity to send must have at least one actor property.");
|
|
12488
|
+
else if (keys.length < 1) throw new TypeError("The keys must not be empty.");
|
|
12489
|
+
return activity.id.href;
|
|
12490
|
+
};
|
|
12491
|
+
const activityId = worker == null ? validateActivity() : await worker.prepare("validation", validateActivity);
|
|
12492
|
+
observation?.setPhase("serialization");
|
|
12493
|
+
const createContextLoader = () => this.contextLoaderFactory(this.#getLoaderOptions(ctx.origin));
|
|
12494
|
+
const contextLoader = worker == null ? createContextLoader() : await worker.prepare("serialization", createContextLoader);
|
|
12495
|
+
const resolveRsaKey = () => {
|
|
12496
|
+
const ldSignable = !require_http.hasPortableActor(activity);
|
|
12497
|
+
let rsaKey = null;
|
|
12498
|
+
for (const { keyId, privateKey } of keys) {
|
|
12499
|
+
require_http.validateCryptoKey(privateKey, "private");
|
|
12500
|
+
if (ldSignable && rsaKey == null && privateKey.algorithm.name === "RSASSA-PKCS1-v1_5" && !require_http.isPortableKeyId(keyId)) rsaKey = {
|
|
12501
|
+
keyId,
|
|
12502
|
+
privateKey
|
|
12503
|
+
};
|
|
12504
|
+
}
|
|
12505
|
+
return {
|
|
12506
|
+
rsaKey,
|
|
12507
|
+
ldSignable
|
|
10802
12508
|
};
|
|
10803
|
-
}
|
|
10804
|
-
|
|
12509
|
+
};
|
|
12510
|
+
observation?.setPhase("key-resolution");
|
|
12511
|
+
const { rsaKey, ldSignable } = worker == null ? resolveRsaKey() : await worker.prepare("key-resolution", resolveRsaKey);
|
|
12512
|
+
observation?.setPhase("signing");
|
|
12513
|
+
const signActivity = () => signOutgoingActivity(activity, keys.map(({ keyId, privateKey }) => ({
|
|
10805
12514
|
verificationMethod: keyId,
|
|
10806
12515
|
privateKey
|
|
10807
12516
|
})), {
|
|
@@ -10809,14 +12518,23 @@ var FederationImpl = class extends FederationBuilderImpl {
|
|
|
10809
12518
|
tracerProvider: this.tracerProvider,
|
|
10810
12519
|
appendToExistingProofs: false
|
|
10811
12520
|
});
|
|
12521
|
+
const signed = worker == null ? await signActivity() : await worker.prepare("signing", signActivity);
|
|
10812
12522
|
activity = signed.activity;
|
|
10813
12523
|
const { hasProof, proofCreated } = signed;
|
|
10814
|
-
|
|
10815
|
-
|
|
10816
|
-
|
|
10817
|
-
|
|
10818
|
-
|
|
10819
|
-
|
|
12524
|
+
observation?.setPhase("serialization");
|
|
12525
|
+
const serializeActivity = async () => {
|
|
12526
|
+
let result = !proofCreated && options.activityJsonLd != null ? options.activityJsonLd : await activity.toJsonLd({
|
|
12527
|
+
format: "compact",
|
|
12528
|
+
contextLoader
|
|
12529
|
+
});
|
|
12530
|
+
if (proofCreated || !hasProof || options.normalizeExistingProofs) result = await require_http.normalizeOutgoingActivityJsonLd(result, contextLoader, { preserveNestedSecuredDocuments: true });
|
|
12531
|
+
return result;
|
|
12532
|
+
};
|
|
12533
|
+
let jsonLd = worker == null ? await serializeActivity() : await worker.prepare("serialization", serializeActivity);
|
|
12534
|
+
observation?.setPhase("validation");
|
|
12535
|
+
const validateProofShape = () => assertSupportedCompoundProofShape(jsonLd, activityId);
|
|
12536
|
+
if (worker == null) validateProofShape();
|
|
12537
|
+
else await worker.prepare("validation", validateProofShape);
|
|
10820
12538
|
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 });
|
|
10821
12539
|
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.", {
|
|
10822
12540
|
activityId,
|
|
@@ -10825,17 +12543,24 @@ var FederationImpl = class extends FederationBuilderImpl {
|
|
|
10825
12543
|
privateKey: pair.privateKey
|
|
10826
12544
|
}))
|
|
10827
12545
|
});
|
|
10828
|
-
else
|
|
10829
|
-
|
|
10830
|
-
|
|
10831
|
-
|
|
10832
|
-
|
|
10833
|
-
|
|
10834
|
-
|
|
10835
|
-
|
|
10836
|
-
|
|
10837
|
-
|
|
10838
|
-
|
|
12546
|
+
else {
|
|
12547
|
+
const signLinkedData = async () => {
|
|
12548
|
+
try {
|
|
12549
|
+
observation?.setPhase("signing");
|
|
12550
|
+
return await require_http.signJsonLd(jsonLd, rsaKey.privateKey, rsaKey.keyId, {
|
|
12551
|
+
contextLoader,
|
|
12552
|
+
tracerProvider: this.tracerProvider
|
|
12553
|
+
});
|
|
12554
|
+
} catch (error) {
|
|
12555
|
+
if (!isLinkedDataSignatureJsonLdProcessingError(error)) throw error;
|
|
12556
|
+
logger.warn("Failed to create a Linked Data signature for the activity {activityId}. The activity will be sent without a Linked Data signature.", {
|
|
12557
|
+
activityId,
|
|
12558
|
+
error
|
|
12559
|
+
});
|
|
12560
|
+
return jsonLd;
|
|
12561
|
+
}
|
|
12562
|
+
};
|
|
12563
|
+
jsonLd = worker == null ? await signLinkedData() : await worker.prepare("signing", signLinkedData);
|
|
10839
12564
|
}
|
|
10840
12565
|
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.", {
|
|
10841
12566
|
activityId,
|
|
@@ -10844,6 +12569,7 @@ var FederationImpl = class extends FederationBuilderImpl {
|
|
|
10844
12569
|
privateKey: pair.privateKey
|
|
10845
12570
|
}))
|
|
10846
12571
|
});
|
|
12572
|
+
observation?.setPhase("request-preparation");
|
|
10847
12573
|
if (immediate || this.outboxQueue == null) {
|
|
10848
12574
|
if (immediate) logger.debug("Sending activity immediately without queue since immediate option is set.", {
|
|
10849
12575
|
activityId: activity.id.href,
|
|
@@ -10854,18 +12580,40 @@ var FederationImpl = class extends FederationBuilderImpl {
|
|
|
10854
12580
|
activity: jsonLd
|
|
10855
12581
|
});
|
|
10856
12582
|
const promises = [];
|
|
10857
|
-
for (const inbox in inboxes)
|
|
10858
|
-
|
|
10859
|
-
|
|
10860
|
-
|
|
10861
|
-
|
|
10862
|
-
|
|
10863
|
-
|
|
10864
|
-
|
|
10865
|
-
|
|
10866
|
-
|
|
10867
|
-
|
|
10868
|
-
|
|
12583
|
+
for (const inbox in inboxes) {
|
|
12584
|
+
const delivery = observation?.delivery(inbox);
|
|
12585
|
+
let sending;
|
|
12586
|
+
try {
|
|
12587
|
+
const createHeaders = collectionSync == null ? void 0 : async () => new Headers({ "Collection-Synchronization": await buildCollectionSynchronizationHeader(collectionSync, inboxes[inbox].actorIds) });
|
|
12588
|
+
const sendActivityId = activity.id?.href;
|
|
12589
|
+
const activityType = (0, _fedify_vocab.getTypeId)(activity).href;
|
|
12590
|
+
observation?.setActivity({
|
|
12591
|
+
...sendActivityId == null ? {} : { id: sendActivityId },
|
|
12592
|
+
type: activityType
|
|
12593
|
+
});
|
|
12594
|
+
sending = sendActivityToInbox(inbox, inboxes[inbox].gatewayInboxes, {
|
|
12595
|
+
attemptStarted: worker == null ? void 0 : (target) => worker.directAttemptStarted(target, observation?.continuation(inbox)?.outbound),
|
|
12596
|
+
observation: delivery,
|
|
12597
|
+
allowPrivateAddress: this.allowPrivateAddress,
|
|
12598
|
+
keys,
|
|
12599
|
+
activity: jsonLd,
|
|
12600
|
+
activityId: sendActivityId,
|
|
12601
|
+
activityType,
|
|
12602
|
+
sharedInbox: inboxes[inbox].sharedInbox,
|
|
12603
|
+
headers: createHeaders == null ? void 0 : await (worker == null ? createHeaders() : worker.prepare("request-preparation", createHeaders)),
|
|
12604
|
+
specDeterminer: new KvSpecDeterminer(this.kv, this.kvPrefixes.httpMessageSignaturesSpec, this.firstKnock, { specTtl: this.httpMessageSignaturesSpecTtl }),
|
|
12605
|
+
meterProvider: this.meterProvider,
|
|
12606
|
+
tracerProvider: this.tracerProvider
|
|
12607
|
+
});
|
|
12608
|
+
} catch (error) {
|
|
12609
|
+
if (delivery != null) await delivery.failed(error);
|
|
12610
|
+
throw error;
|
|
12611
|
+
}
|
|
12612
|
+
if (observation != null) sending.catch(() => {});
|
|
12613
|
+
if (delivery != null) await delivery.handoff({ type: "direct" });
|
|
12614
|
+
promises.push(sending);
|
|
12615
|
+
}
|
|
12616
|
+
if (observation != null) await observation.prepared("delivery");
|
|
10869
12617
|
await Promise.all(promises);
|
|
10870
12618
|
return;
|
|
10871
12619
|
}
|
|
@@ -10873,48 +12621,71 @@ var FederationImpl = class extends FederationBuilderImpl {
|
|
|
10873
12621
|
activityId: activity.id.href,
|
|
10874
12622
|
activity: jsonLd
|
|
10875
12623
|
});
|
|
12624
|
+
observation?.setPhase("key-resolution");
|
|
10876
12625
|
const keyJwkPairs = [];
|
|
10877
12626
|
for (const { keyId, privateKey } of keys) {
|
|
10878
|
-
const
|
|
12627
|
+
const exportPrivateKey = () => require_http.exportJwk(privateKey);
|
|
12628
|
+
const privateKeyJwk = worker == null ? await exportPrivateKey() : await worker.prepare("key-export", exportPrivateKey);
|
|
10879
12629
|
keyJwkPairs.push({
|
|
10880
12630
|
keyId: keyId.href,
|
|
10881
12631
|
privateKey: privateKeyJwk
|
|
10882
12632
|
});
|
|
10883
12633
|
}
|
|
10884
12634
|
if (!this.manuallyStartQueue) this._startQueueInternal(ctx.data);
|
|
12635
|
+
observation?.setPhase("request-preparation");
|
|
10885
12636
|
const carrier = {};
|
|
10886
|
-
_opentelemetry_api.propagation.inject(_opentelemetry_api.context.active(), carrier);
|
|
12637
|
+
const injectTraceContext = () => _opentelemetry_api.propagation.inject(_opentelemetry_api.context.active(), carrier);
|
|
12638
|
+
if (worker == null) injectTraceContext();
|
|
12639
|
+
else await worker.prepare("request-preparation", injectTraceContext);
|
|
10887
12640
|
const messages = [];
|
|
10888
|
-
for (const inbox in inboxes) {
|
|
10889
|
-
const
|
|
10890
|
-
|
|
10891
|
-
|
|
10892
|
-
|
|
10893
|
-
|
|
10894
|
-
|
|
10895
|
-
|
|
10896
|
-
|
|
10897
|
-
|
|
10898
|
-
|
|
10899
|
-
|
|
10900
|
-
|
|
10901
|
-
|
|
10902
|
-
|
|
10903
|
-
|
|
10904
|
-
|
|
10905
|
-
|
|
10906
|
-
|
|
10907
|
-
|
|
12641
|
+
for (const inbox in inboxes) try {
|
|
12642
|
+
const prepareMessage = async () => {
|
|
12643
|
+
const { portableInbox, gatewayInboxes } = inboxes[inbox];
|
|
12644
|
+
const messageOrderingKey = orderingKey == null ? void 0 : `${orderingKey}\n${getOrderingDestination(inbox, portableInbox)}`;
|
|
12645
|
+
const messageActivityId = activity.id?.href;
|
|
12646
|
+
const messageActivityType = (0, _fedify_vocab.getTypeId)(activity).href;
|
|
12647
|
+
observation?.setActivity({
|
|
12648
|
+
...messageActivityId == null ? {} : { id: messageActivityId },
|
|
12649
|
+
type: messageActivityType
|
|
12650
|
+
});
|
|
12651
|
+
return {
|
|
12652
|
+
message: {
|
|
12653
|
+
type: "outbox",
|
|
12654
|
+
...serializeProvenance(provenance),
|
|
12655
|
+
id: crypto.randomUUID(),
|
|
12656
|
+
baseUrl: ctx.origin,
|
|
12657
|
+
keys: keyJwkPairs,
|
|
12658
|
+
activity: jsonLd,
|
|
12659
|
+
activityId: messageActivityId,
|
|
12660
|
+
activityType: messageActivityType,
|
|
12661
|
+
inbox,
|
|
12662
|
+
...observation?.continuation(inbox),
|
|
12663
|
+
sharedInbox: inboxes[inbox].sharedInbox,
|
|
12664
|
+
actorIds: [...inboxes[inbox].actorIds],
|
|
12665
|
+
started: (/* @__PURE__ */ new Date()).toISOString(),
|
|
12666
|
+
attempt: 0,
|
|
12667
|
+
headers: collectionSync == null ? {} : { "Collection-Synchronization": await buildCollectionSynchronizationHeader(collectionSync, inboxes[inbox].actorIds) },
|
|
12668
|
+
orderingKey: messageOrderingKey,
|
|
12669
|
+
...getPortableMessageFields(portableInbox, gatewayInboxes),
|
|
12670
|
+
traceContext: carrier
|
|
12671
|
+
},
|
|
12672
|
+
orderingKey: messageOrderingKey
|
|
12673
|
+
};
|
|
10908
12674
|
};
|
|
10909
|
-
messages.push(
|
|
10910
|
-
|
|
10911
|
-
|
|
10912
|
-
|
|
12675
|
+
messages.push(await (worker == null ? prepareMessage() : worker.prepare("request-preparation", prepareMessage)));
|
|
12676
|
+
} catch (error) {
|
|
12677
|
+
if (observation != null) await observation.delivery(inbox)?.failed(error);
|
|
12678
|
+
throw error;
|
|
10913
12679
|
}
|
|
12680
|
+
if (observation != null) await observation.prepared("delivery");
|
|
10914
12681
|
const { outboxQueue } = this;
|
|
10915
12682
|
if (outboxQueue.enqueueMany == null || orderingKey != null) {
|
|
10916
12683
|
const errors = (await Promise.allSettled(messages.map(async (m) => {
|
|
10917
|
-
|
|
12684
|
+
const enqueue = () => worker == null ? outboxQueue.enqueue(m.message, { orderingKey: m.orderingKey }) : worker.enqueueChildren([m.message], () => outboxQueue.enqueue(m.message, { orderingKey: m.orderingKey }));
|
|
12685
|
+
await (observation == null ? enqueue() : observation.enqueue([{
|
|
12686
|
+
inbox: m.message.inbox,
|
|
12687
|
+
queueMessageId: m.message.id
|
|
12688
|
+
}], "outbox", enqueue));
|
|
10918
12689
|
require_http.recordOutboxEnqueue(this.meterProvider, outboxQueue, m.message);
|
|
10919
12690
|
}))).filter((r) => r.status === "rejected").map((r) => r.reason);
|
|
10920
12691
|
if (errors.length > 0) {
|
|
@@ -10927,7 +12698,11 @@ var FederationImpl = class extends FederationBuilderImpl {
|
|
|
10927
12698
|
}
|
|
10928
12699
|
} else {
|
|
10929
12700
|
try {
|
|
10930
|
-
|
|
12701
|
+
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)));
|
|
12702
|
+
await (observation == null ? enqueue() : observation.enqueue(messages.map(({ message }) => ({
|
|
12703
|
+
inbox: message.inbox,
|
|
12704
|
+
queueMessageId: message.id
|
|
12705
|
+
})), "outbox", enqueue));
|
|
10931
12706
|
} catch (error) {
|
|
10932
12707
|
logger.error("Failed to enqueue activity {activityId} to send later: {error}", {
|
|
10933
12708
|
activityId: activity.id.href,
|
|
@@ -10939,9 +12714,16 @@ var FederationImpl = class extends FederationBuilderImpl {
|
|
|
10939
12714
|
}
|
|
10940
12715
|
}
|
|
10941
12716
|
fetch(request, options) {
|
|
10942
|
-
|
|
12717
|
+
const requestId = getRequestId(request);
|
|
12718
|
+
return (0, _logtape_logtape.withContext)({
|
|
12719
|
+
requestId,
|
|
12720
|
+
inboxRequestId: void 0,
|
|
12721
|
+
dispatchId: void 0,
|
|
12722
|
+
sourceInboxRequestId: void 0,
|
|
12723
|
+
sourceRequestId: void 0
|
|
12724
|
+
}, async () => {
|
|
10943
12725
|
const tracer = this._getTracer();
|
|
10944
|
-
const metricState = {};
|
|
12726
|
+
const metricState = { requestId };
|
|
10945
12727
|
const metricStart = performance.now();
|
|
10946
12728
|
return await tracer.startActiveSpan(request.method, {
|
|
10947
12729
|
kind: _opentelemetry_api.SpanKind.SERVER,
|
|
@@ -10986,7 +12768,8 @@ var FederationImpl = class extends FederationBuilderImpl {
|
|
|
10986
12768
|
logger.error("An error occurred while serving request {method} {url}: {error}", {
|
|
10987
12769
|
method: request.method,
|
|
10988
12770
|
url: request.url,
|
|
10989
|
-
error
|
|
12771
|
+
error,
|
|
12772
|
+
...metricState.inboxRequest == null ? {} : { inboxRequestId: metricState.inboxRequest.inboxRequestId }
|
|
10990
12773
|
});
|
|
10991
12774
|
throw error;
|
|
10992
12775
|
}
|
|
@@ -11006,6 +12789,7 @@ var FederationImpl = class extends FederationBuilderImpl {
|
|
|
11006
12789
|
const url = new URL(request.url);
|
|
11007
12790
|
const logTpl = "{method} {path}: {status}";
|
|
11008
12791
|
const values = {
|
|
12792
|
+
...metricState.inboxRequest == null ? {} : { inboxRequestId: metricState.inboxRequest.inboxRequestId },
|
|
11009
12793
|
method: request.method,
|
|
11010
12794
|
path: `${url.pathname}${url.search}`,
|
|
11011
12795
|
url: request.url,
|
|
@@ -11058,9 +12842,11 @@ var FederationImpl = class extends FederationBuilderImpl {
|
|
|
11058
12842
|
metricState.endpoint = getEndpointCategory(route.name);
|
|
11059
12843
|
span.updateName(`${request.method} ${route.template}`);
|
|
11060
12844
|
const routeName = route.name.replace(/:.*$/, "");
|
|
12845
|
+
const source = request.method === "POST" && (routeName === "inbox" || routeName === "sharedInbox") ? this.#createInboxRequestSource(request, metricState) : void 0;
|
|
12846
|
+
const provenance = globalThis.Object.freeze(source == null ? { originatingRequestId: metricState.requestId } : { inboxRequest: source });
|
|
11061
12847
|
let context;
|
|
11062
12848
|
try {
|
|
11063
|
-
context = this.#createContext(request, contextData);
|
|
12849
|
+
context = (0, _logtape_logtape.withContext)(source == null ? {} : { inboxRequestId: source.inboxRequestId }, () => this.#createContext(request, contextData, { [outboundProvenance]: provenance }));
|
|
11064
12850
|
} catch (error) {
|
|
11065
12851
|
if (request.method !== "POST" || routeName !== "inbox" && routeName !== "sharedInbox") throw error;
|
|
11066
12852
|
return await this.#reportInboxPreparationError(request, contextData, {
|
|
@@ -11131,13 +12917,19 @@ var FederationImpl = class extends FederationBuilderImpl {
|
|
|
11131
12917
|
const observation = new InboxObservation({
|
|
11132
12918
|
kind: routeName === "inbox" ? "personal" : "shared",
|
|
11133
12919
|
recipient
|
|
11134
|
-
});
|
|
12920
|
+
}, source);
|
|
11135
12921
|
this.#setInboxCompletion(metricState, observation, () => context);
|
|
11136
|
-
return await observation.run(async () => {
|
|
11137
|
-
if (routeName === "inbox") context = this.#createContext(request, contextData, {
|
|
12922
|
+
return await (0, _logtape_logtape.withContext)({ inboxRequestId: source.inboxRequestId }, () => observation.run(async () => {
|
|
12923
|
+
if (routeName === "inbox") context = this.#createContext(request, contextData, {
|
|
12924
|
+
[outboundProvenance]: getOutboundProvenance(context),
|
|
12925
|
+
documentLoader: await context.getDocumentLoader({ identifier: recipient })
|
|
12926
|
+
});
|
|
11138
12927
|
else if (this.sharedInboxKeyDispatcher != null) {
|
|
11139
12928
|
const identity = await this.sharedInboxKeyDispatcher(context);
|
|
11140
|
-
if (identity != null) context = this.#createContext(request, contextData, {
|
|
12929
|
+
if (identity != null) context = this.#createContext(request, contextData, {
|
|
12930
|
+
[outboundProvenance]: getOutboundProvenance(context),
|
|
12931
|
+
documentLoader: "identifier" in identity || "username" in identity ? await context.getDocumentLoader(identity) : context.getDocumentLoader(identity)
|
|
12932
|
+
});
|
|
11141
12933
|
}
|
|
11142
12934
|
return await this.#handleInbox(request, {
|
|
11143
12935
|
recipient,
|
|
@@ -11146,13 +12938,16 @@ var FederationImpl = class extends FederationBuilderImpl {
|
|
|
11146
12938
|
onNotFound,
|
|
11147
12939
|
observation
|
|
11148
12940
|
});
|
|
11149
|
-
}, span);
|
|
12941
|
+
}, span));
|
|
11150
12942
|
}
|
|
11151
12943
|
switch (routeName) {
|
|
11152
12944
|
case "actor":
|
|
11153
12945
|
case "actorAlias": {
|
|
11154
12946
|
const identifier = route.name.startsWith("actorAlias:") ? route.name.substring(11) : route.values.identifier;
|
|
11155
|
-
context = this.#createContext(request, contextData, {
|
|
12947
|
+
context = this.#createContext(request, contextData, {
|
|
12948
|
+
[outboundProvenance]: getOutboundProvenance(context),
|
|
12949
|
+
invokedFromActorDispatcher: { identifier }
|
|
12950
|
+
});
|
|
11156
12951
|
return await handleActor(request, {
|
|
11157
12952
|
identifier,
|
|
11158
12953
|
context,
|
|
@@ -11166,10 +12961,13 @@ var FederationImpl = class extends FederationBuilderImpl {
|
|
|
11166
12961
|
const typeId = route.name.replace(/^object:/, "");
|
|
11167
12962
|
const callbacks = this.objectCallbacks[typeId];
|
|
11168
12963
|
const cls = this.objectTypeIds[typeId];
|
|
11169
|
-
context = this.#createContext(request, contextData, {
|
|
11170
|
-
|
|
11171
|
-
|
|
11172
|
-
|
|
12964
|
+
context = this.#createContext(request, contextData, {
|
|
12965
|
+
[outboundProvenance]: getOutboundProvenance(context),
|
|
12966
|
+
invokedFromObjectDispatcher: {
|
|
12967
|
+
cls,
|
|
12968
|
+
values: route.values
|
|
12969
|
+
}
|
|
12970
|
+
});
|
|
11173
12971
|
return await handleObject(request, {
|
|
11174
12972
|
values: route.values,
|
|
11175
12973
|
context,
|
|
@@ -11227,7 +13025,10 @@ var FederationImpl = class extends FederationBuilderImpl {
|
|
|
11227
13025
|
case "sharedInbox":
|
|
11228
13026
|
if (this.sharedInboxKeyDispatcher != null) {
|
|
11229
13027
|
const identity = await this.sharedInboxKeyDispatcher(context);
|
|
11230
|
-
if (identity != null) context = this.#createContext(request, contextData, {
|
|
13028
|
+
if (identity != null) context = this.#createContext(request, contextData, {
|
|
13029
|
+
[outboundProvenance]: getOutboundProvenance(context),
|
|
13030
|
+
documentLoader: "identifier" in identity || "username" in identity ? await context.getDocumentLoader(identity) : context.getDocumentLoader(identity)
|
|
13031
|
+
});
|
|
11231
13032
|
}
|
|
11232
13033
|
return await this.#handleInbox(request, {
|
|
11233
13034
|
recipient: route.values.identifier ?? null,
|
|
@@ -11371,14 +13172,17 @@ var FederationImpl = class extends FederationBuilderImpl {
|
|
|
11371
13172
|
});
|
|
11372
13173
|
return response;
|
|
11373
13174
|
}
|
|
13175
|
+
const provenance = globalThis.Object.freeze({ originatingRequestId: metricState.requestId });
|
|
11374
13176
|
if (collectionRoute != null) return await this.#fetchPortableCollection(request, portable, route, {
|
|
11375
13177
|
onNotFound,
|
|
11376
13178
|
onUnauthorized,
|
|
11377
|
-
contextData
|
|
13179
|
+
contextData,
|
|
13180
|
+
provenance
|
|
11378
13181
|
});
|
|
11379
13182
|
if (isActor) {
|
|
11380
13183
|
const identifier = route.name.startsWith("actorAlias:") ? route.name.substring(11) : route.values.identifier;
|
|
11381
13184
|
const context = this.#createContext(request, contextData, {
|
|
13185
|
+
[outboundProvenance]: provenance,
|
|
11382
13186
|
invokedFromActorDispatcher: { identifier },
|
|
11383
13187
|
portableRequest: portable.portableRequest
|
|
11384
13188
|
});
|
|
@@ -11399,6 +13203,7 @@ var FederationImpl = class extends FederationBuilderImpl {
|
|
|
11399
13203
|
const callbacks = this.objectCallbacks[typeId];
|
|
11400
13204
|
const cls = this.objectTypeIds[typeId];
|
|
11401
13205
|
const context = this.#createContext(request, contextData, {
|
|
13206
|
+
[outboundProvenance]: provenance,
|
|
11402
13207
|
invokedFromObjectDispatcher: {
|
|
11403
13208
|
cls,
|
|
11404
13209
|
values: route.values
|
|
@@ -11416,7 +13221,7 @@ var FederationImpl = class extends FederationBuilderImpl {
|
|
|
11416
13221
|
onNotFound
|
|
11417
13222
|
});
|
|
11418
13223
|
}
|
|
11419
|
-
async #fetchPortableCollection(request, portable, route, { onNotFound, onUnauthorized, contextData }) {
|
|
13224
|
+
async #fetchPortableCollection(request, portable, route, { onNotFound, onUnauthorized, contextData, provenance }) {
|
|
11420
13225
|
const logger = (0, _logtape_logtape.getLogger)([
|
|
11421
13226
|
"fedify",
|
|
11422
13227
|
"federation",
|
|
@@ -11424,8 +13229,11 @@ var FederationImpl = class extends FederationBuilderImpl {
|
|
|
11424
13229
|
]);
|
|
11425
13230
|
const url = new URL(request.url);
|
|
11426
13231
|
const { authority } = portable.portableRequest;
|
|
11427
|
-
const ownerContext = this.#createContext(request, contextData);
|
|
11428
|
-
const context = this.#createContext(request, contextData, {
|
|
13232
|
+
const ownerContext = this.#createContext(request, contextData, { [outboundProvenance]: provenance });
|
|
13233
|
+
const context = this.#createContext(request, contextData, {
|
|
13234
|
+
[outboundProvenance]: provenance,
|
|
13235
|
+
portableRequest: portable.portableRequest
|
|
13236
|
+
});
|
|
11429
13237
|
const reject = async (reason, identifier) => {
|
|
11430
13238
|
logger.debug("Not serving the portable collection {collection} of the actor {identifier}: {reason}.", {
|
|
11431
13239
|
collection: portable.canonicalId,
|
|
@@ -11518,6 +13326,11 @@ var FederationImpl = class extends FederationBuilderImpl {
|
|
|
11518
13326
|
}
|
|
11519
13327
|
return await onNotFound(request);
|
|
11520
13328
|
}
|
|
13329
|
+
#createInboxRequestSource(request, metricState) {
|
|
13330
|
+
const source = createInboxRequestSource(request, metricState.requestId ?? getRequestId(request), this.origin?.webOrigin);
|
|
13331
|
+
metricState.inboxRequest = source;
|
|
13332
|
+
return source;
|
|
13333
|
+
}
|
|
11521
13334
|
#setInboxCompletion(metricState, observation, getContext) {
|
|
11522
13335
|
const handler = this.inboxRequestFinishedHandler;
|
|
11523
13336
|
metricState.inboxCompletion = {
|
|
@@ -11549,6 +13362,7 @@ var FederationImpl = class extends FederationBuilderImpl {
|
|
|
11549
13362
|
async #reportInboxPreparationError(request, contextData, inbox, error, span, metricState) {
|
|
11550
13363
|
const unavailableLoader = () => Promise.reject(error);
|
|
11551
13364
|
const context = new RequestContextImpl({
|
|
13365
|
+
[outboundProvenance]: globalThis.Object.freeze({ inboxRequest: metricState.inboxRequest }),
|
|
11552
13366
|
url: new URL(request.url),
|
|
11553
13367
|
request,
|
|
11554
13368
|
federation: this,
|
|
@@ -11556,7 +13370,7 @@ var FederationImpl = class extends FederationBuilderImpl {
|
|
|
11556
13370
|
documentLoader: unavailableLoader,
|
|
11557
13371
|
contextLoader: unavailableLoader
|
|
11558
13372
|
});
|
|
11559
|
-
const observation = new InboxObservation(inbox);
|
|
13373
|
+
const observation = new InboxObservation(inbox, metricState.inboxRequest);
|
|
11560
13374
|
this.#setInboxCompletion(metricState, observation, () => context);
|
|
11561
13375
|
return await observation.run(() => Promise.reject(error), span);
|
|
11562
13376
|
}
|
|
@@ -11614,9 +13428,10 @@ var FederationImpl = class extends FederationBuilderImpl {
|
|
|
11614
13428
|
metricState.endpoint = "inbox";
|
|
11615
13429
|
span.updateName(`${request.method} ${metricState.routeTemplate}`);
|
|
11616
13430
|
const identifier = route.values.identifier;
|
|
13431
|
+
const source = this.#createInboxRequestSource(request, metricState);
|
|
11617
13432
|
let context;
|
|
11618
13433
|
try {
|
|
11619
|
-
context = this.#createContext(request, contextData);
|
|
13434
|
+
context = (0, _logtape_logtape.withContext)({ inboxRequestId: source.inboxRequestId }, () => this.#createContext(request, contextData, { [outboundProvenance]: globalThis.Object.freeze({ inboxRequest: source }) }));
|
|
11620
13435
|
} catch (error) {
|
|
11621
13436
|
return await this.#reportInboxPreparationError(request, contextData, {
|
|
11622
13437
|
kind: "portable",
|
|
@@ -11626,9 +13441,9 @@ var FederationImpl = class extends FederationBuilderImpl {
|
|
|
11626
13441
|
const observation = new InboxObservation({
|
|
11627
13442
|
kind: "portable",
|
|
11628
13443
|
recipient: identifier
|
|
11629
|
-
});
|
|
13444
|
+
}, source);
|
|
11630
13445
|
this.#setInboxCompletion(metricState, observation, () => context);
|
|
11631
|
-
return await observation.run(async () => {
|
|
13446
|
+
return await (0, _logtape_logtape.withContext)({ inboxRequestId: source.inboxRequestId }, () => observation.run(async () => {
|
|
11632
13447
|
const actor = this.actorCallbacks?.dispatcher == null ? null : await this.actorCallbacks.dispatcher(context, identifier);
|
|
11633
13448
|
const localOrigin = context.canonicalOrigin;
|
|
11634
13449
|
const resolution = resolvePortableInboxRecipient(actor == null || actor instanceof _fedify_vocab.Tombstone ? null : actor, {
|
|
@@ -11645,7 +13460,10 @@ var FederationImpl = class extends FederationBuilderImpl {
|
|
|
11645
13460
|
return await onNotFound(request);
|
|
11646
13461
|
}
|
|
11647
13462
|
const { recipient } = resolution;
|
|
11648
|
-
if (this.actorCallbacks?.keyPairsDispatcher != null) context = this.#createContext(request, contextData, {
|
|
13463
|
+
if (this.actorCallbacks?.keyPairsDispatcher != null) context = this.#createContext(request, contextData, {
|
|
13464
|
+
[outboundProvenance]: getOutboundProvenance(context),
|
|
13465
|
+
documentLoader: await context.getDocumentLoader({ identifier })
|
|
13466
|
+
});
|
|
11649
13467
|
const excludedOrigins = [localOrigin, new URL(request.url).origin];
|
|
11650
13468
|
return await this.#handleInbox(request, {
|
|
11651
13469
|
recipient: identifier,
|
|
@@ -11655,16 +13473,25 @@ var FederationImpl = class extends FederationBuilderImpl {
|
|
|
11655
13473
|
onNotFound,
|
|
11656
13474
|
portableInbox: {
|
|
11657
13475
|
recipient,
|
|
11658
|
-
forward:
|
|
11659
|
-
|
|
13476
|
+
forward: (activity, activityId, activityType, signatures) => observeOutboundBroadcast(context, this.outboundObservers, async (observation) => {
|
|
13477
|
+
observation?.setActivity({
|
|
13478
|
+
...activityId == null ? {} : { id: activityId.href },
|
|
13479
|
+
type: activityType
|
|
13480
|
+
});
|
|
13481
|
+
if (this.portableInboxForwarding.maxTargets < 1) {
|
|
13482
|
+
if (observation != null) await observation.skipped("forwarding-disabled");
|
|
13483
|
+
return;
|
|
13484
|
+
}
|
|
11660
13485
|
if (this.kv.cas == null) {
|
|
11661
13486
|
if (!this.#portableInboxForwardingWarned) {
|
|
11662
13487
|
this.#portableInboxForwardingWarned = true;
|
|
11663
13488
|
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.");
|
|
11664
13489
|
}
|
|
13490
|
+
if (observation != null) await observation.skipped("forwarding-unavailable");
|
|
11665
13491
|
return;
|
|
11666
13492
|
}
|
|
11667
13493
|
await forwardPortableInboxActivity({
|
|
13494
|
+
provenance: getOutboundProvenance(context),
|
|
11668
13495
|
recipient,
|
|
11669
13496
|
activity,
|
|
11670
13497
|
activityId,
|
|
@@ -11682,11 +13509,11 @@ var FederationImpl = class extends FederationBuilderImpl {
|
|
|
11682
13509
|
options: this.portableInboxForwarding,
|
|
11683
13510
|
meterProvider: this.meterProvider,
|
|
11684
13511
|
tracerProvider: this.tracerProvider
|
|
11685
|
-
});
|
|
11686
|
-
}
|
|
13512
|
+
}, observation);
|
|
13513
|
+
}, "portable-replication")
|
|
11687
13514
|
}
|
|
11688
13515
|
});
|
|
11689
|
-
}, span);
|
|
13516
|
+
}, span));
|
|
11690
13517
|
}
|
|
11691
13518
|
/**
|
|
11692
13519
|
* Gets this server's gateway key pairs for the portable actor that owns
|
|
@@ -11756,7 +13583,7 @@ var FederationImpl = class extends FederationBuilderImpl {
|
|
|
11756
13583
|
headers: { "Content-Type": "text/plain; charset=utf-8" }
|
|
11757
13584
|
});
|
|
11758
13585
|
}
|
|
11759
|
-
const context = this.#createContext(request, contextData);
|
|
13586
|
+
const context = this.#createContext(request, contextData, { [outboundProvenance]: globalThis.Object.freeze({ originatingRequestId: metricState.requestId }) });
|
|
11760
13587
|
const response = await this.hashlinkMediaDispatcher(context, media.media);
|
|
11761
13588
|
if (response == null) return await onNotFound(request);
|
|
11762
13589
|
return await discardBodyForHead(request, response, false);
|
|
@@ -11898,8 +13725,9 @@ var ContextImpl = class ContextImpl {
|
|
|
11898
13725
|
verifyPortableObject;
|
|
11899
13726
|
invokedFromActorKeyPairsDispatcher;
|
|
11900
13727
|
#codec;
|
|
11901
|
-
constructor({ url, federation, data, documentLoader, contextLoader, invokedFromActorKeyPairsDispatcher }) {
|
|
13728
|
+
constructor({ url, federation, data, documentLoader, contextLoader, invokedFromActorKeyPairsDispatcher, [outboundProvenance]: provenance = emptyProvenance }) {
|
|
11902
13729
|
this.url = url;
|
|
13730
|
+
globalThis.Object.assign(this, { [outboundProvenance]: provenance });
|
|
11903
13731
|
this.federation = federation;
|
|
11904
13732
|
this.data = data;
|
|
11905
13733
|
this.documentLoader = documentLoader;
|
|
@@ -11921,6 +13749,7 @@ var ContextImpl = class ContextImpl {
|
|
|
11921
13749
|
}
|
|
11922
13750
|
clone(data) {
|
|
11923
13751
|
return new ContextImpl({
|
|
13752
|
+
[outboundProvenance]: getOutboundProvenance(this),
|
|
11924
13753
|
url: this.url,
|
|
11925
13754
|
federation: this.federation,
|
|
11926
13755
|
data,
|
|
@@ -11931,6 +13760,7 @@ var ContextImpl = class ContextImpl {
|
|
|
11931
13760
|
}
|
|
11932
13761
|
toInboxContext(recipient, activity, activityId, activityType, metadata) {
|
|
11933
13762
|
return new InboxContextImpl(recipient, activity, activityId, activityType, {
|
|
13763
|
+
[outboundProvenance]: getOutboundProvenance(this),
|
|
11934
13764
|
url: this.url,
|
|
11935
13765
|
federation: this.federation,
|
|
11936
13766
|
data: this.data,
|
|
@@ -11941,6 +13771,7 @@ var ContextImpl = class ContextImpl {
|
|
|
11941
13771
|
}
|
|
11942
13772
|
toOutboxContext(identifier, activity, activityId, activityType) {
|
|
11943
13773
|
return new OutboxContextImpl(identifier, activity, activityId, activityType, {
|
|
13774
|
+
[outboundProvenance]: getOutboundProvenance(this),
|
|
11944
13775
|
url: this.url,
|
|
11945
13776
|
federation: this.federation,
|
|
11946
13777
|
data: this.data,
|
|
@@ -12330,36 +14161,49 @@ var ContextImpl = class ContextImpl {
|
|
|
12330
14161
|
await this.#enqueueTasks(task, payloads, options);
|
|
12331
14162
|
}
|
|
12332
14163
|
sendActivity(sender, recipients, activity, options = {}) {
|
|
12333
|
-
return this
|
|
12334
|
-
|
|
12335
|
-
|
|
12336
|
-
|
|
12337
|
-
|
|
12338
|
-
|
|
12339
|
-
|
|
12340
|
-
|
|
12341
|
-
|
|
12342
|
-
|
|
12343
|
-
|
|
12344
|
-
|
|
12345
|
-
|
|
12346
|
-
|
|
12347
|
-
|
|
12348
|
-
|
|
12349
|
-
|
|
12350
|
-
|
|
12351
|
-
|
|
12352
|
-
|
|
12353
|
-
|
|
12354
|
-
|
|
12355
|
-
|
|
14164
|
+
return observeOutboundBroadcast(this, this.federation.outboundObservers, (observation, provenance) => {
|
|
14165
|
+
const tracer = this.tracerProvider.getTracer(require_http.name, require_http.version);
|
|
14166
|
+
const parentSpanContext = _opentelemetry_api.trace.getSpanContext(_opentelemetry_api.context.active());
|
|
14167
|
+
const activityType = (0, _fedify_vocab.getTypeId)(activity).href;
|
|
14168
|
+
observation?.setActivity({ type: activityType });
|
|
14169
|
+
return tracer.startActiveSpan(this.federation.outboxQueue == null || options.immediate ? "activitypub.outbox" : "activitypub.fanout", {
|
|
14170
|
+
kind: this.federation.outboxQueue == null || options.immediate ? _opentelemetry_api.SpanKind.CLIENT : _opentelemetry_api.SpanKind.PRODUCER,
|
|
14171
|
+
attributes: {
|
|
14172
|
+
"activitypub.activity.type": activityType,
|
|
14173
|
+
"activitypub.activity.to": activity.toIds.map((to) => to.href),
|
|
14174
|
+
"activitypub.activity.cc": activity.ccIds.map((cc) => cc.href),
|
|
14175
|
+
"activitypub.activity.bto": activity.btoIds.map((bto) => bto.href),
|
|
14176
|
+
"activitypub.activity.bcc": activity.bccIds.map((bcc) => bcc.href)
|
|
14177
|
+
}
|
|
14178
|
+
}, async (span) => {
|
|
14179
|
+
observation?.setSpan(span, parentSpanContext);
|
|
14180
|
+
try {
|
|
14181
|
+
const activityId = activity.id?.href;
|
|
14182
|
+
if (activityId != null) span.setAttribute("activitypub.activity.id", activityId);
|
|
14183
|
+
observation?.setActivity({
|
|
14184
|
+
type: activityType,
|
|
14185
|
+
...activityId == null ? {} : { id: activityId }
|
|
14186
|
+
});
|
|
14187
|
+
await this.sendActivityInternal(sender, recipients, activity, options, span, observation, provenance);
|
|
14188
|
+
} catch (e) {
|
|
14189
|
+
span.setStatus({
|
|
14190
|
+
code: _opentelemetry_api.SpanStatusCode.ERROR,
|
|
14191
|
+
message: String(e)
|
|
14192
|
+
});
|
|
14193
|
+
throw e;
|
|
14194
|
+
} finally {
|
|
14195
|
+
span.end();
|
|
14196
|
+
}
|
|
14197
|
+
});
|
|
14198
|
+
}, "send", options.dispatchId);
|
|
12356
14199
|
}
|
|
12357
|
-
async sendActivityInternal(sender, recipients, activity, options, span) {
|
|
14200
|
+
async sendActivityInternal(sender, recipients, activity, options, span, observation, provenance = getOutboundProvenance(this)) {
|
|
12358
14201
|
const logger = (0, _logtape_logtape.getLogger)([
|
|
12359
14202
|
"fedify",
|
|
12360
14203
|
"federation",
|
|
12361
14204
|
"outbox"
|
|
12362
14205
|
]);
|
|
14206
|
+
observation?.setPhase("sender-resolution");
|
|
12363
14207
|
let keys;
|
|
12364
14208
|
let identifier = null;
|
|
12365
14209
|
let actorKeyPairs = null;
|
|
@@ -12375,6 +14219,7 @@ var ContextImpl = class ContextImpl {
|
|
|
12375
14219
|
}
|
|
12376
14220
|
}
|
|
12377
14221
|
span.setAttribute("fedify.actor.identifier", identifier);
|
|
14222
|
+
observation?.setPhase("key-resolution");
|
|
12378
14223
|
if (this.federation.actorCallbacks?.keyPairsDispatcher == null) throw new Error("No actor key pairs dispatcher registered.");
|
|
12379
14224
|
actorKeyPairs = await this.getActorKeyPairs(identifier);
|
|
12380
14225
|
if (actorKeyPairs.length < 1) throw new Error(`No key pair found for actor ${JSON.stringify(identifier)}.`);
|
|
@@ -12383,11 +14228,14 @@ var ContextImpl = class ContextImpl {
|
|
|
12383
14228
|
privateKey: kp.privateKey
|
|
12384
14229
|
}));
|
|
12385
14230
|
} else if (Array.isArray(sender)) {
|
|
14231
|
+
observation?.setPhase("key-resolution");
|
|
12386
14232
|
if (sender.length < 1) throw new Error("The sender's key pairs are empty.");
|
|
12387
14233
|
keys = sender;
|
|
12388
14234
|
} else keys = [sender];
|
|
12389
14235
|
if (keys.length < 1) throw new TypeError("The sender's keys must not be empty.");
|
|
14236
|
+
observation?.setPhase("key-resolution");
|
|
12390
14237
|
for (const { privateKey } of keys) require_http.validateCryptoKey(privateKey, "private");
|
|
14238
|
+
observation?.setPhase("recipient-resolution");
|
|
12391
14239
|
let expandedRecipients;
|
|
12392
14240
|
let collectionSync;
|
|
12393
14241
|
if (Array.isArray(recipients)) expandedRecipients = recipients;
|
|
@@ -12403,14 +14251,34 @@ var ContextImpl = class ContextImpl {
|
|
|
12403
14251
|
} else expandedRecipients = [recipients];
|
|
12404
14252
|
const opts = {
|
|
12405
14253
|
context: this,
|
|
14254
|
+
observation,
|
|
14255
|
+
provenance,
|
|
12406
14256
|
orderingKey: options.orderingKey,
|
|
12407
14257
|
collectionSync,
|
|
12408
14258
|
immediate: options.immediate,
|
|
12409
14259
|
normalizeExistingProofs: options.normalizeExistingProofs
|
|
12410
14260
|
};
|
|
12411
14261
|
span.setAttribute("activitypub.inboxes", expandedRecipients.length);
|
|
14262
|
+
observation?.setPhase("transformation");
|
|
14263
|
+
if (this.federation.activityTransformers.length > 0) observation?.setActivity({});
|
|
12412
14264
|
for (const activityTransformer of this.federation.activityTransformers) activity = activityTransformer(activity, this);
|
|
12413
|
-
|
|
14265
|
+
let transformedActivityId;
|
|
14266
|
+
try {
|
|
14267
|
+
transformedActivityId = activity?.id?.href;
|
|
14268
|
+
} finally {
|
|
14269
|
+
if (observation != null) {
|
|
14270
|
+
let activityType;
|
|
14271
|
+
try {
|
|
14272
|
+
activityType = (0, _fedify_vocab.getTypeId)(activity)?.href;
|
|
14273
|
+
} catch {}
|
|
14274
|
+
observation.setActivity({
|
|
14275
|
+
...transformedActivityId == null ? {} : { id: transformedActivityId },
|
|
14276
|
+
...activityType == null ? {} : { type: activityType }
|
|
14277
|
+
});
|
|
14278
|
+
}
|
|
14279
|
+
}
|
|
14280
|
+
span?.setAttribute("activitypub.activity.id", transformedActivityId ?? "");
|
|
14281
|
+
observation?.setPhase("validation");
|
|
12414
14282
|
if (activity.actorId == null) {
|
|
12415
14283
|
logger.error("Activity {activityId} to send does not have an actor.", {
|
|
12416
14284
|
activity,
|
|
@@ -12418,6 +14286,7 @@ var ContextImpl = class ContextImpl {
|
|
|
12418
14286
|
});
|
|
12419
14287
|
throw new TypeError("The activity to send must have at least one actor property.");
|
|
12420
14288
|
}
|
|
14289
|
+
observation?.setPhase("signing");
|
|
12421
14290
|
const { activity: signedActivity, proofCreated } = await signOutgoingActivity(activity, actorKeyPairs == null ? keys.map(({ keyId, privateKey }) => ({
|
|
12422
14291
|
verificationMethod: keyId,
|
|
12423
14292
|
privateKey
|
|
@@ -12430,16 +14299,19 @@ var ContextImpl = class ContextImpl {
|
|
|
12430
14299
|
appendToExistingProofs: actorKeyPairs != null
|
|
12431
14300
|
});
|
|
12432
14301
|
activity = signedActivity;
|
|
14302
|
+
observation?.setPhase("destination-planning");
|
|
12433
14303
|
const inboxes = extractInboxes({
|
|
12434
14304
|
recipients: expandedRecipients,
|
|
12435
14305
|
preferSharedInbox: options.preferSharedInbox,
|
|
12436
14306
|
excludeBaseUris: options.excludeBaseUris
|
|
12437
14307
|
});
|
|
14308
|
+
if (observation != null) await observation.planDestinations(inboxes);
|
|
12438
14309
|
if (globalThis.Object.keys(inboxes).length < 1) {
|
|
12439
14310
|
logger.debug("No inboxes found for activity {activityId}.", {
|
|
12440
14311
|
activityId: activity.id?.href,
|
|
12441
14312
|
activity
|
|
12442
14313
|
});
|
|
14314
|
+
if (observation != null) await observation.skipped();
|
|
12443
14315
|
return false;
|
|
12444
14316
|
}
|
|
12445
14317
|
logger.debug("Sending activity {activityId} to inboxes:\n{inboxes}", {
|
|
@@ -12447,6 +14319,7 @@ var ContextImpl = class ContextImpl {
|
|
|
12447
14319
|
activityId: activity.id?.href,
|
|
12448
14320
|
activity
|
|
12449
14321
|
});
|
|
14322
|
+
observation?.setPhase("serialization");
|
|
12450
14323
|
const activityJsonLd = await activity.toJsonLd({
|
|
12451
14324
|
format: "compact",
|
|
12452
14325
|
contextLoader: this.contextLoader
|
|
@@ -12467,33 +14340,48 @@ var ContextImpl = class ContextImpl {
|
|
|
12467
14340
|
});
|
|
12468
14341
|
return true;
|
|
12469
14342
|
}
|
|
14343
|
+
observation?.setPhase("validation");
|
|
12470
14344
|
assertSupportedCompoundProofShape(activityJsonLd, activity.id?.href);
|
|
14345
|
+
observation?.setPhase("key-resolution");
|
|
12471
14346
|
const keyJwkPairs = await Promise.all(keys.map(async ({ keyId, privateKey }) => ({
|
|
12472
14347
|
keyId: keyId.href,
|
|
12473
14348
|
privateKey: await require_http.exportJwk(privateKey)
|
|
12474
14349
|
})));
|
|
14350
|
+
observation?.setPhase("request-preparation");
|
|
12475
14351
|
const carrier = {};
|
|
12476
14352
|
_opentelemetry_api.propagation.inject(_opentelemetry_api.context.active(), carrier);
|
|
14353
|
+
const activityId = activity.id?.href;
|
|
14354
|
+
const activityType = (0, _fedify_vocab.getTypeId)(activity).href;
|
|
14355
|
+
observation?.setActivity({
|
|
14356
|
+
...activityId == null ? {} : { id: activityId },
|
|
14357
|
+
type: activityType
|
|
14358
|
+
});
|
|
12477
14359
|
const message = {
|
|
12478
14360
|
type: "fanout",
|
|
14361
|
+
...serializeProvenance(provenance),
|
|
12479
14362
|
id: crypto.randomUUID(),
|
|
12480
14363
|
baseUrl: this.origin,
|
|
12481
14364
|
keys: keyJwkPairs,
|
|
12482
14365
|
inboxes: globalThis.Object.fromEntries(globalThis.Object.entries(inboxes).map(([k, { actorIds, sharedInbox, portableInbox, gatewayInboxes }]) => [k, {
|
|
12483
14366
|
actorIds: [...actorIds],
|
|
14367
|
+
...observation?.continuation(k),
|
|
12484
14368
|
sharedInbox,
|
|
12485
14369
|
...getPortableMessageFields(portableInbox, gatewayInboxes)
|
|
12486
14370
|
}])),
|
|
12487
14371
|
activity: activityJsonLd,
|
|
12488
|
-
activityId
|
|
12489
|
-
activityType
|
|
14372
|
+
activityId,
|
|
14373
|
+
activityType,
|
|
12490
14374
|
collectionSync: opts.collectionSync,
|
|
12491
14375
|
orderingKey: options.orderingKey,
|
|
12492
14376
|
normalizeExistingProofs: proofCreated || options.normalizeExistingProofs,
|
|
12493
14377
|
traceContext: carrier
|
|
12494
14378
|
};
|
|
12495
14379
|
if (!this.federation.manuallyStartQueue) this.federation._startQueueInternal(this.data);
|
|
12496
|
-
|
|
14380
|
+
if (observation != null) await observation.prepared("fanout");
|
|
14381
|
+
await (observation == null ? this.federation.fanoutQueue.enqueue(message, { orderingKey: options.orderingKey }) : observation.enqueue(globalThis.Object.keys(inboxes).map((inbox) => ({
|
|
14382
|
+
inbox,
|
|
14383
|
+
queueMessageId: message.id
|
|
14384
|
+
})), "fanout", () => this.federation.fanoutQueue.enqueue(message, { orderingKey: options.orderingKey })));
|
|
12497
14385
|
require_http.getFederationMetrics(this.federation.meterProvider).recordQueueTaskEnqueued({
|
|
12498
14386
|
role: "fanout",
|
|
12499
14387
|
queue: this.federation.fanoutQueue,
|
|
@@ -12754,6 +14642,7 @@ var RequestContextImpl = class RequestContextImpl extends ContextImpl {
|
|
|
12754
14642
|
}
|
|
12755
14643
|
clone(data) {
|
|
12756
14644
|
return new RequestContextImpl({
|
|
14645
|
+
[outboundProvenance]: getOutboundProvenance(this),
|
|
12757
14646
|
url: this.url,
|
|
12758
14647
|
federation: this.federation,
|
|
12759
14648
|
data,
|
|
@@ -12925,30 +14814,40 @@ var RequestContextImpl = class RequestContextImpl extends ContextImpl {
|
|
|
12925
14814
|
}
|
|
12926
14815
|
};
|
|
12927
14816
|
function forwardActivity(ctx, loggerCategory, forwarder, recipients, options) {
|
|
12928
|
-
return ctx
|
|
12929
|
-
|
|
12930
|
-
|
|
12931
|
-
|
|
12932
|
-
|
|
12933
|
-
|
|
12934
|
-
|
|
12935
|
-
}
|
|
12936
|
-
|
|
12937
|
-
|
|
12938
|
-
|
|
12939
|
-
|
|
12940
|
-
|
|
12941
|
-
|
|
12942
|
-
|
|
12943
|
-
|
|
12944
|
-
|
|
14817
|
+
return observeOutboundBroadcast(ctx, ctx.federation.outboundObservers, (observation, provenance) => {
|
|
14818
|
+
observation?.setActivity({
|
|
14819
|
+
...ctx.activityId == null ? {} : { id: ctx.activityId },
|
|
14820
|
+
type: ctx.activityType
|
|
14821
|
+
});
|
|
14822
|
+
const tracer = ctx.tracerProvider.getTracer(require_http.name, require_http.version);
|
|
14823
|
+
const parentSpanContext = _opentelemetry_api.trace.getSpanContext(_opentelemetry_api.context.active());
|
|
14824
|
+
return tracer.startActiveSpan(ctx.federation.outboxQueue == null || options?.immediate ? `activitypub.${loggerCategory}` : "activitypub.fanout", {
|
|
14825
|
+
kind: ctx.federation.outboxQueue == null || options?.immediate ? _opentelemetry_api.SpanKind.CLIENT : _opentelemetry_api.SpanKind.PRODUCER,
|
|
14826
|
+
attributes: { "activitypub.activity.type": ctx.activityType }
|
|
14827
|
+
}, async (span) => {
|
|
14828
|
+
observation?.setSpan(span, parentSpanContext);
|
|
14829
|
+
try {
|
|
14830
|
+
if (ctx.activityId != null) span.setAttribute("activitypub.activity.id", ctx.activityId);
|
|
14831
|
+
return await forwardActivityInternal(ctx, loggerCategory, forwarder, recipients, options, observation, provenance);
|
|
14832
|
+
} catch (e) {
|
|
14833
|
+
span.setStatus({
|
|
14834
|
+
code: _opentelemetry_api.SpanStatusCode.ERROR,
|
|
14835
|
+
message: String(e)
|
|
14836
|
+
});
|
|
14837
|
+
throw e;
|
|
14838
|
+
} finally {
|
|
14839
|
+
span.end();
|
|
14840
|
+
}
|
|
14841
|
+
});
|
|
14842
|
+
}, "forward", options?.dispatchId);
|
|
12945
14843
|
}
|
|
12946
|
-
async function forwardActivityInternal(ctx, loggerCategory, forwarder, recipients, options) {
|
|
14844
|
+
async function forwardActivityInternal(ctx, loggerCategory, forwarder, recipients, options, observation, provenance = getOutboundProvenance(ctx)) {
|
|
12947
14845
|
const logger = (0, _logtape_logtape.getLogger)([
|
|
12948
14846
|
"fedify",
|
|
12949
14847
|
"federation",
|
|
12950
14848
|
loggerCategory
|
|
12951
14849
|
]);
|
|
14850
|
+
observation?.setPhase("sender-resolution");
|
|
12952
14851
|
let keys;
|
|
12953
14852
|
let identifier = null;
|
|
12954
14853
|
if ("identifier" in forwarder || "username" in forwarder) {
|
|
@@ -12962,6 +14861,7 @@ async function forwardActivityInternal(ctx, loggerCategory, forwarder, recipient
|
|
|
12962
14861
|
identifier = mapped;
|
|
12963
14862
|
}
|
|
12964
14863
|
}
|
|
14864
|
+
observation?.setPhase("key-resolution");
|
|
12965
14865
|
const actorKeyPairs = await ctx.getActorKeyPairs(identifier);
|
|
12966
14866
|
if (actorKeyPairs.length < 1) throw new Error(`No key pair found for actor ${JSON.stringify(identifier)}.`);
|
|
12967
14867
|
keys = actorKeyPairs.map((kp) => ({
|
|
@@ -12969,12 +14869,17 @@ async function forwardActivityInternal(ctx, loggerCategory, forwarder, recipient
|
|
|
12969
14869
|
privateKey: kp.privateKey
|
|
12970
14870
|
}));
|
|
12971
14871
|
} else if (Array.isArray(forwarder)) {
|
|
14872
|
+
observation?.setPhase("key-resolution");
|
|
12972
14873
|
if (forwarder.length < 1) throw new Error("The forwarder's key pairs are empty.");
|
|
12973
14874
|
keys = forwarder;
|
|
12974
14875
|
} else keys = [forwarder];
|
|
14876
|
+
observation?.setPhase("validation");
|
|
12975
14877
|
if (!require_http.hasSignatureLike(ctx.activity)) {
|
|
12976
14878
|
if (!require_http.hasProofLike(ctx.activity)) {
|
|
12977
|
-
if (options?.skipIfUnsigned)
|
|
14879
|
+
if (options?.skipIfUnsigned) {
|
|
14880
|
+
if (observation != null) await observation.skipped("unsigned");
|
|
14881
|
+
return false;
|
|
14882
|
+
}
|
|
12978
14883
|
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.", {
|
|
12979
14884
|
activityId: ctx.activityId,
|
|
12980
14885
|
activityType: ctx.activityType,
|
|
@@ -12982,23 +14887,27 @@ async function forwardActivityInternal(ctx, loggerCategory, forwarder, recipient
|
|
|
12982
14887
|
});
|
|
12983
14888
|
}
|
|
12984
14889
|
}
|
|
14890
|
+
observation?.setPhase("recipient-resolution");
|
|
12985
14891
|
if (recipients === "followers") {
|
|
12986
14892
|
if (identifier == null) throw new Error("If recipients is \"followers\", forwarder must be an actor identifier or username.");
|
|
12987
14893
|
const followers = [];
|
|
12988
14894
|
for await (const recipient of ctx.getFollowers(identifier)) followers.push(recipient);
|
|
12989
14895
|
recipients = followers;
|
|
12990
14896
|
}
|
|
14897
|
+
observation?.setPhase("destination-planning");
|
|
12991
14898
|
const inboxes = extractInboxes({
|
|
12992
14899
|
recipients: Array.isArray(recipients) ? recipients : [recipients],
|
|
12993
14900
|
preferSharedInbox: options?.preferSharedInbox,
|
|
12994
14901
|
excludeBaseUris: options?.excludeBaseUris
|
|
12995
14902
|
});
|
|
14903
|
+
if (observation != null) await observation.planDestinations(inboxes);
|
|
12996
14904
|
if (globalThis.Object.keys(inboxes).length < 1) {
|
|
12997
14905
|
logger.debug("No inboxes found for activity {activityId}.", {
|
|
12998
14906
|
activityId: ctx.activityId,
|
|
12999
14907
|
activityType: ctx.activityType,
|
|
13000
14908
|
identifier: identifier ?? void 0
|
|
13001
14909
|
});
|
|
14910
|
+
if (observation != null) await observation.skipped();
|
|
13002
14911
|
return false;
|
|
13003
14912
|
}
|
|
13004
14913
|
logger.debug("Forwarding activity {activityId} to inboxes:\n{inboxes}", {
|
|
@@ -13009,18 +14918,33 @@ async function forwardActivityInternal(ctx, loggerCategory, forwarder, recipient
|
|
|
13009
14918
|
if (options?.immediate || ctx.federation.outboxQueue == null) {
|
|
13010
14919
|
if (options?.immediate) logger.debug("Forwarding activity immediately without queue since immediate option is set.");
|
|
13011
14920
|
else logger.debug("Forwarding activity immediately without queue since queue is not set.");
|
|
14921
|
+
observation?.setPhase("request-preparation");
|
|
13012
14922
|
const promises = [];
|
|
13013
|
-
for (const inbox in inboxes)
|
|
13014
|
-
|
|
13015
|
-
|
|
13016
|
-
|
|
13017
|
-
|
|
13018
|
-
|
|
13019
|
-
|
|
13020
|
-
|
|
13021
|
-
|
|
13022
|
-
|
|
13023
|
-
|
|
14923
|
+
for (const inbox in inboxes) {
|
|
14924
|
+
const delivery = observation?.delivery(inbox);
|
|
14925
|
+
let sending;
|
|
14926
|
+
try {
|
|
14927
|
+
sending = sendActivityToInbox(inbox, inboxes[inbox].gatewayInboxes, {
|
|
14928
|
+
observation: delivery,
|
|
14929
|
+
allowPrivateAddress: ctx.federation.allowPrivateAddress,
|
|
14930
|
+
keys,
|
|
14931
|
+
activity: ctx.activity,
|
|
14932
|
+
activityId: ctx.activityId,
|
|
14933
|
+
activityType: ctx.activityType,
|
|
14934
|
+
sharedInbox: inboxes[inbox].sharedInbox,
|
|
14935
|
+
meterProvider: ctx.meterProvider,
|
|
14936
|
+
tracerProvider: ctx.tracerProvider,
|
|
14937
|
+
specDeterminer: new KvSpecDeterminer(ctx.federation.kv, ctx.federation.kvPrefixes.httpMessageSignaturesSpec, ctx.federation.firstKnock, { specTtl: ctx.federation.httpMessageSignaturesSpecTtl })
|
|
14938
|
+
});
|
|
14939
|
+
} catch (error) {
|
|
14940
|
+
if (delivery != null) await delivery.failed(error);
|
|
14941
|
+
throw error;
|
|
14942
|
+
}
|
|
14943
|
+
if (observation != null) sending.catch(() => {});
|
|
14944
|
+
if (delivery != null) await delivery.handoff({ type: "direct" });
|
|
14945
|
+
promises.push(sending);
|
|
14946
|
+
}
|
|
14947
|
+
if (observation != null) await observation.prepared("delivery");
|
|
13024
14948
|
await Promise.all(promises);
|
|
13025
14949
|
return true;
|
|
13026
14950
|
}
|
|
@@ -13028,7 +14952,9 @@ async function forwardActivityInternal(ctx, loggerCategory, forwarder, recipient
|
|
|
13028
14952
|
activityId: ctx.activityId,
|
|
13029
14953
|
activity: ctx.activity
|
|
13030
14954
|
});
|
|
14955
|
+
observation?.setPhase("request-preparation");
|
|
13031
14956
|
if (!ctx.federation.manuallyStartQueue) ctx.federation._startQueueInternal(ctx.data);
|
|
14957
|
+
observation?.setPhase("key-resolution");
|
|
13032
14958
|
const keyJwkPairs = [];
|
|
13033
14959
|
for (const { keyId, privateKey } of keys) {
|
|
13034
14960
|
const privateKeyJwk = await require_http.exportJwk(privateKey);
|
|
@@ -13037,15 +14963,17 @@ async function forwardActivityInternal(ctx, loggerCategory, forwarder, recipient
|
|
|
13037
14963
|
privateKey: privateKeyJwk
|
|
13038
14964
|
});
|
|
13039
14965
|
}
|
|
14966
|
+
observation?.setPhase("request-preparation");
|
|
13040
14967
|
const carrier = {};
|
|
13041
14968
|
_opentelemetry_api.propagation.inject(_opentelemetry_api.context.active(), carrier);
|
|
13042
14969
|
const orderingKey = options?.orderingKey;
|
|
13043
14970
|
const started = (/* @__PURE__ */ new Date()).toISOString();
|
|
13044
14971
|
const messages = [];
|
|
13045
|
-
for (const inbox in inboxes) {
|
|
14972
|
+
for (const inbox in inboxes) try {
|
|
13046
14973
|
const { portableInbox, gatewayInboxes } = inboxes[inbox];
|
|
13047
14974
|
const message = {
|
|
13048
14975
|
type: "outbox",
|
|
14976
|
+
...serializeProvenance(provenance),
|
|
13049
14977
|
id: crypto.randomUUID(),
|
|
13050
14978
|
baseUrl: ctx.origin,
|
|
13051
14979
|
keys: keyJwkPairs,
|
|
@@ -13053,6 +14981,7 @@ async function forwardActivityInternal(ctx, loggerCategory, forwarder, recipient
|
|
|
13053
14981
|
activityId: ctx.activityId,
|
|
13054
14982
|
activityType: ctx.activityType,
|
|
13055
14983
|
inbox,
|
|
14984
|
+
...observation?.continuation(inbox),
|
|
13056
14985
|
sharedInbox: inboxes[inbox].sharedInbox,
|
|
13057
14986
|
actorIds: [...inboxes[inbox].actorIds],
|
|
13058
14987
|
started,
|
|
@@ -13066,11 +14995,18 @@ async function forwardActivityInternal(ctx, loggerCategory, forwarder, recipient
|
|
|
13066
14995
|
message,
|
|
13067
14996
|
orderingKey: message.orderingKey
|
|
13068
14997
|
});
|
|
14998
|
+
} catch (error) {
|
|
14999
|
+
if (observation != null) await observation.delivery(inbox)?.failed(error);
|
|
15000
|
+
throw error;
|
|
13069
15001
|
}
|
|
15002
|
+
if (observation != null) await observation.prepared("delivery");
|
|
13070
15003
|
const { outboxQueue } = ctx.federation;
|
|
13071
15004
|
if (outboxQueue.enqueueMany == null || orderingKey != null) {
|
|
13072
15005
|
const errors = (await Promise.allSettled(messages.map(async (m) => {
|
|
13073
|
-
await outboxQueue.enqueue(m.message, { orderingKey: m.orderingKey })
|
|
15006
|
+
await (observation == null ? outboxQueue.enqueue(m.message, { orderingKey: m.orderingKey }) : observation.enqueue([{
|
|
15007
|
+
inbox: m.message.inbox,
|
|
15008
|
+
queueMessageId: m.message.id
|
|
15009
|
+
}], "outbox", () => outboxQueue.enqueue(m.message, { orderingKey: m.orderingKey })));
|
|
13074
15010
|
require_http.recordOutboxEnqueue(ctx.federation.meterProvider, outboxQueue, m.message);
|
|
13075
15011
|
}))).filter((r) => r.status === "rejected").map((r) => r.reason);
|
|
13076
15012
|
if (errors.length > 0) {
|
|
@@ -13083,7 +15019,10 @@ async function forwardActivityInternal(ctx, loggerCategory, forwarder, recipient
|
|
|
13083
15019
|
}
|
|
13084
15020
|
} else {
|
|
13085
15021
|
try {
|
|
13086
|
-
await outboxQueue.enqueueMany(messages.map((m) => m.message))
|
|
15022
|
+
await (observation == null ? outboxQueue.enqueueMany(messages.map((m) => m.message)) : observation.enqueue(messages.map(({ message }) => ({
|
|
15023
|
+
inbox: message.inbox,
|
|
15024
|
+
queueMessageId: message.id
|
|
15025
|
+
})), "outbox", () => outboxQueue.enqueueMany(messages.map((m) => m.message))));
|
|
13087
15026
|
} catch (error) {
|
|
13088
15027
|
logger.error("Failed to enqueue activity {activityId} to forward later:\n{error}", {
|
|
13089
15028
|
activityId: ctx.activityId,
|
|
@@ -13119,6 +15058,7 @@ var InboxContextImpl = class InboxContextImpl extends ContextImpl {
|
|
|
13119
15058
|
}
|
|
13120
15059
|
clone(data) {
|
|
13121
15060
|
return new InboxContextImpl(this.recipient, this.activity, this.activityId, this.activityType, {
|
|
15061
|
+
[outboundProvenance]: getOutboundProvenance(this),
|
|
13122
15062
|
url: this.url,
|
|
13123
15063
|
federation: this.federation,
|
|
13124
15064
|
data,
|
|
@@ -13149,29 +15089,41 @@ var OutboxContextImpl = class OutboxContextImpl extends ContextImpl {
|
|
|
13149
15089
|
return this.#deliveryState.delivered;
|
|
13150
15090
|
}
|
|
13151
15091
|
sendActivity(sender, recipients, activity, options = {}) {
|
|
13152
|
-
return this
|
|
13153
|
-
|
|
13154
|
-
|
|
13155
|
-
|
|
13156
|
-
|
|
13157
|
-
|
|
13158
|
-
|
|
13159
|
-
|
|
13160
|
-
|
|
13161
|
-
|
|
13162
|
-
|
|
13163
|
-
|
|
13164
|
-
|
|
13165
|
-
|
|
13166
|
-
|
|
13167
|
-
|
|
13168
|
-
|
|
13169
|
-
|
|
13170
|
-
|
|
13171
|
-
|
|
13172
|
-
|
|
13173
|
-
|
|
13174
|
-
|
|
15092
|
+
return observeOutboundBroadcast(this, this.federation.outboundObservers, (observation, provenance) => {
|
|
15093
|
+
const tracer = this.tracerProvider.getTracer(require_http.name, require_http.version);
|
|
15094
|
+
const parentSpanContext = _opentelemetry_api.trace.getSpanContext(_opentelemetry_api.context.active());
|
|
15095
|
+
const activityType = (0, _fedify_vocab.getTypeId)(activity).href;
|
|
15096
|
+
observation?.setActivity({ type: activityType });
|
|
15097
|
+
return tracer.startActiveSpan(this.federation.outboxQueue == null || options.immediate ? "activitypub.outbox" : "activitypub.fanout", {
|
|
15098
|
+
kind: this.federation.outboxQueue == null || options.immediate ? _opentelemetry_api.SpanKind.CLIENT : _opentelemetry_api.SpanKind.PRODUCER,
|
|
15099
|
+
attributes: {
|
|
15100
|
+
"activitypub.activity.type": activityType,
|
|
15101
|
+
"activitypub.activity.to": activity.toIds.map((to) => to.href),
|
|
15102
|
+
"activitypub.activity.cc": activity.ccIds.map((cc) => cc.href),
|
|
15103
|
+
"activitypub.activity.bto": activity.btoIds.map((bto) => bto.href),
|
|
15104
|
+
"activitypub.activity.bcc": activity.bccIds.map((bcc) => bcc.href)
|
|
15105
|
+
}
|
|
15106
|
+
}, async (span) => {
|
|
15107
|
+
observation?.setSpan(span, parentSpanContext);
|
|
15108
|
+
try {
|
|
15109
|
+
const activityId = activity.id?.href;
|
|
15110
|
+
if (activityId != null) span.setAttribute("activitypub.activity.id", activityId);
|
|
15111
|
+
observation?.setActivity({
|
|
15112
|
+
type: activityType,
|
|
15113
|
+
...activityId == null ? {} : { id: activityId }
|
|
15114
|
+
});
|
|
15115
|
+
if (await this.sendActivityInternal(sender, recipients, activity, options, span, observation, provenance)) this.#deliveryState.delivered = true;
|
|
15116
|
+
} catch (e) {
|
|
15117
|
+
span.setStatus({
|
|
15118
|
+
code: _opentelemetry_api.SpanStatusCode.ERROR,
|
|
15119
|
+
message: String(e)
|
|
15120
|
+
});
|
|
15121
|
+
throw e;
|
|
15122
|
+
} finally {
|
|
15123
|
+
span.end();
|
|
15124
|
+
}
|
|
15125
|
+
});
|
|
15126
|
+
}, "send", options.dispatchId);
|
|
13175
15127
|
}
|
|
13176
15128
|
forwardActivity(forwarder, recipients, options) {
|
|
13177
15129
|
return forwardActivity(this, "outbox", forwarder, recipients, options).then((delivered) => {
|
|
@@ -13180,6 +15132,7 @@ var OutboxContextImpl = class OutboxContextImpl extends ContextImpl {
|
|
|
13180
15132
|
}
|
|
13181
15133
|
clone(data) {
|
|
13182
15134
|
return new OutboxContextImpl(this.identifier, this.activity, this.activityId, this.activityType, {
|
|
15135
|
+
[outboundProvenance]: getOutboundProvenance(this),
|
|
13183
15136
|
url: this.url,
|
|
13184
15137
|
federation: this.federation,
|
|
13185
15138
|
data,
|