@fedify/fedify 2.3.0-dev.1110 → 2.3.0-dev.1119
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/{assert_rejects-B-qJtC9Z.mjs → assert_rejects-DQP-q39h.mjs} +27 -2
- package/dist/{builder-B-Y6fwSu.mjs → builder-Ond_h57y.mjs} +3 -3
- package/dist/compat/mod.d.cts +1 -1
- package/dist/compat/mod.d.ts +1 -1
- package/dist/compat/outgoing-jsonld.test.mjs +1 -1
- package/dist/compat/public-audience.test.mjs +1 -1
- package/dist/compat/transformers.test.mjs +2 -2
- package/dist/{context-C0C_sRha.d.cts → context-Ch-ZLyTQ.d.cts} +1 -1
- package/dist/{context-Dqgt8saU.d.ts → context-cSUMk2da.d.ts} +1 -1
- package/dist/{deno-hqC7tKJn.mjs → deno-DVsHS7rA.mjs} +1 -1
- package/dist/{docloader-BOEuuXkX.mjs → docloader-WsWfKaE5.mjs} +2 -2
- package/dist/federation/builder.test.mjs +3 -3
- package/dist/federation/collection.test.mjs +2 -2
- package/dist/federation/handler.test.mjs +8 -7
- package/dist/federation/idempotency.test.mjs +5 -5
- package/dist/federation/inbox.test.mjs +1 -1
- package/dist/federation/keycache.test.mjs +1 -1
- package/dist/federation/kv.test.mjs +2 -2
- package/dist/federation/metrics.test.d.mts +2 -0
- package/dist/federation/metrics.test.mjs +107 -0
- package/dist/federation/middleware.test.mjs +390 -10
- package/dist/federation/mod.cjs +1 -1
- package/dist/federation/mod.d.cts +2 -2
- package/dist/federation/mod.d.ts +2 -2
- package/dist/federation/mod.js +1 -1
- package/dist/federation/mq.test.mjs +2 -2
- package/dist/federation/negotiation.test.mjs +2 -2
- package/dist/federation/router.test.mjs +2 -2
- package/dist/federation/send.test.mjs +11 -11
- package/dist/federation/webfinger.test.mjs +3 -3
- package/dist/{getMachineId-bsd-etIyxDet.mjs → getMachineId-bsd-BY01PL1n.mjs} +1 -1
- package/dist/{getMachineId-darwin-D23zTf4g.mjs → getMachineId-darwin-Dr1gkBkp.mjs} +1 -1
- package/dist/{getMachineId-win-Dpap6v5i.mjs → getMachineId-win-QEYwcJiy.mjs} +1 -1
- package/dist/{http-O8MYWwk8.js → http-CouJSFVK.js} +461 -37
- package/dist/{http-DV0il3vk.cjs → http-CubOB9wq.cjs} +513 -35
- package/dist/{http-BDZeS5om.d.ts → http-D6LP89UO.d.ts} +7 -1
- package/dist/{http-C87EWkO0.d.cts → http-D6aw3j2U.d.cts} +7 -1
- package/dist/{http-BLopFpvC.mjs → http-DUV8ysti.mjs} +86 -37
- package/dist/{key-DW1EVmtP.mjs → key-BoWaYRHm.mjs} +1 -1
- package/dist/{kv-cache-C3NWWiTg.js → kv-cache-DBNpsneh.js} +1 -1
- package/dist/{kv-cache-Dya-TWMe.cjs → kv-cache-Dz31ATUT.cjs} +1 -1
- package/dist/{ld-BNkk2Yal.mjs → ld-B5K1mSuG.mjs} +60 -9
- package/dist/{send-hokVCPu6.mjs → metrics-C4attqv0.mjs} +124 -224
- package/dist/{middleware-D6FbOjuK.mjs → middleware-BDKFRjue.mjs} +1 -1
- package/dist/{middleware-DUWeXjZR.cjs → middleware-CmsDtIHI.cjs} +75 -309
- package/dist/{middleware-CjzI3aYo.js → middleware-Dtjz-hSk.js} +46 -280
- package/dist/{middleware-DA2WTBr4.mjs → middleware-t0jC8I99.mjs} +59 -34
- package/dist/{mod-DXY9JF28.d.cts → mod-B-Lin9Sy.d.ts} +25 -2
- package/dist/{mod-DHO9lk3D.d.ts → mod-BDhgfjP7.d.cts} +25 -2
- package/dist/{mod-B0rWmfW5.d.cts → mod-BR_BB0bh.d.cts} +1 -1
- package/dist/{mod-Dx3-hqyo.d.ts → mod-C6E8rkcz.d.ts} +1 -1
- package/dist/{mod-BhU_H1I_.d.ts → mod-DLrRb0dx.d.ts} +1 -1
- package/dist/{mod-CLPnQPsv.d.cts → mod-P9tE2WmM.d.cts} +1 -1
- package/dist/mod.cjs +4 -4
- package/dist/mod.d.cts +5 -5
- package/dist/mod.d.ts +5 -5
- package/dist/mod.js +4 -4
- package/dist/nodeinfo/client.test.mjs +2 -2
- package/dist/nodeinfo/handler.test.mjs +3 -3
- package/dist/nodeinfo/types.test.mjs +2 -2
- package/dist/otel/exporter.test.mjs +2 -2
- package/dist/{outgoing-jsonld-BgFLCJQ_.mjs → outgoing-jsonld-BNL8AC14.mjs} +1 -1
- package/dist/{owner-jvJAtR5O.mjs → owner-hDxI0ufu.mjs} +2 -2
- package/dist/{proof-BD92WeqV.cjs → proof-BUWfVr6Q.cjs} +78 -11
- package/dist/{proof-mfmHH9j0.mjs → proof-DhVuz4bc.mjs} +25 -7
- package/dist/{proof-5kT7OUPV.js → proof-n60t8o9P.js} +78 -11
- package/dist/send-BPhyR5Oo.mjs +225 -0
- package/dist/sig/accept.test.mjs +1 -1
- package/dist/sig/http.test.mjs +212 -6
- package/dist/sig/key.test.mjs +4 -4
- package/dist/sig/ld.test.mjs +138 -5
- package/dist/sig/mod.cjs +2 -2
- package/dist/sig/mod.d.cts +2 -2
- package/dist/sig/mod.d.ts +2 -2
- package/dist/sig/mod.js +2 -2
- package/dist/sig/owner.test.mjs +4 -4
- package/dist/sig/proof.test.mjs +167 -6
- package/dist/{std__assert-CRDpx_HF.mjs → std__assert-BTEgfoJo.mjs} +2 -27
- package/dist/utils/docloader.test.mjs +5 -5
- package/dist/utils/kv-cache.test.mjs +1 -1
- package/dist/utils/mod.cjs +1 -1
- package/dist/utils/mod.d.cts +1 -1
- package/dist/utils/mod.d.ts +1 -1
- package/dist/utils/mod.js +1 -1
- package/package.json +5 -5
- /package/dist/{accept-CceiKpCy.mjs → accept-CgDcxvjV.mjs} +0 -0
- /package/dist/{activity-listener-tztVvlNb.mjs → activity-listener-BeTGV3wc.mjs} +0 -0
- /package/dist/{client-B_A6mfn3.mjs → client-Bneh_DYR.mjs} +0 -0
- /package/dist/{collection-CA3V5zyK.mjs → collection-Cc3DVAhE.mjs} +0 -0
- /package/dist/{execAsync-DCBrgFiV.mjs → execAsync-Dxb7rNf3.mjs} +0 -0
- /package/dist/{getMachineId-linux-ObI47Hql.mjs → getMachineId-linux-Bbhofx-s.mjs} +0 -0
- /package/dist/{getMachineId-unsupported-Ddu-PFeh.mjs → getMachineId-unsupported-dIOte2Ct.mjs} +0 -0
- /package/dist/{keys-C3kae-6B.mjs → keys-CSYsOMFG.mjs} +0 -0
- /package/dist/{kv-x2IvBUyq.mjs → kv-QHE0oeM3.mjs} +0 -0
- /package/dist/{kv-cache-CiiNwT6W.mjs → kv-cache-DihufyAQ.mjs} +0 -0
- /package/dist/{public-audience-N3pyOx2p.mjs → public-audience-c9zmYKgA.mjs} +0 -0
- /package/dist/{types-BFowWFTT.mjs → types-D09GN0uZ.mjs} +0 -0
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
import "@js-temporal/polyfill";
|
|
2
2
|
import "urlpattern-polyfill";
|
|
3
3
|
globalThis.addEventListener = () => {};
|
|
4
|
-
import { n as version, t as name } from "./deno-
|
|
5
|
-
import {
|
|
6
|
-
import { getLogger } from "@logtape/logtape";
|
|
7
|
-
import { SpanKind, SpanStatusCode, metrics, trace } from "@opentelemetry/api";
|
|
4
|
+
import { n as version, t as name } from "./deno-DVsHS7rA.mjs";
|
|
5
|
+
import { metrics } from "@opentelemetry/api";
|
|
8
6
|
//#region src/federation/metrics.ts
|
|
9
7
|
var FederationMetrics = class {
|
|
10
8
|
deliverySent;
|
|
11
9
|
deliveryPermanentFailure;
|
|
12
10
|
signatureVerificationFailure;
|
|
11
|
+
signatureVerificationDuration;
|
|
12
|
+
signatureKeyFetchDuration;
|
|
13
13
|
deliveryDuration;
|
|
14
14
|
inboxProcessingDuration;
|
|
15
15
|
httpServerRequestCount;
|
|
@@ -20,6 +20,9 @@ var FederationMetrics = class {
|
|
|
20
20
|
queueTaskFailed;
|
|
21
21
|
queueTaskDuration;
|
|
22
22
|
queueTaskInFlight;
|
|
23
|
+
fanoutRecipients;
|
|
24
|
+
inboxActivity;
|
|
25
|
+
outboxActivity;
|
|
23
26
|
constructor(meterProvider) {
|
|
24
27
|
const meter = meterProvider.getMeter(name, version);
|
|
25
28
|
this.deliverySent = meter.createCounter("activitypub.delivery.sent", {
|
|
@@ -34,6 +37,14 @@ var FederationMetrics = class {
|
|
|
34
37
|
description: "ActivityPub signature verification failures.",
|
|
35
38
|
unit: "{failure}"
|
|
36
39
|
});
|
|
40
|
+
this.signatureVerificationDuration = meter.createHistogram("activitypub.signature.verification.duration", {
|
|
41
|
+
description: "Duration of ActivityPub signature verification, including local key lookup and remote key fetches.",
|
|
42
|
+
unit: "ms"
|
|
43
|
+
});
|
|
44
|
+
this.signatureKeyFetchDuration = meter.createHistogram("activitypub.signature.key_fetch.duration", {
|
|
45
|
+
description: "Duration of public key lookup performed during ActivityPub signature verification.",
|
|
46
|
+
unit: "ms"
|
|
47
|
+
});
|
|
37
48
|
this.deliveryDuration = meter.createHistogram("activitypub.delivery.duration", {
|
|
38
49
|
description: "Duration of ActivityPub delivery attempts.",
|
|
39
50
|
unit: "ms"
|
|
@@ -106,6 +117,18 @@ var FederationMetrics = class {
|
|
|
106
117
|
description: "Queue tasks currently being processed in this Fedify process.",
|
|
107
118
|
unit: "{task}"
|
|
108
119
|
});
|
|
120
|
+
this.fanoutRecipients = meter.createHistogram("activitypub.fanout.recipients", {
|
|
121
|
+
description: "Number of recipient inboxes produced by an ActivityPub fanout task.",
|
|
122
|
+
unit: "{recipient}"
|
|
123
|
+
});
|
|
124
|
+
this.inboxActivity = meter.createCounter("activitypub.inbox.activity", {
|
|
125
|
+
description: "ActivityPub activities observed at the inbox lifecycle level: queued, processed, retried, rejected, or abandoned.",
|
|
126
|
+
unit: "{activity}"
|
|
127
|
+
});
|
|
128
|
+
this.outboxActivity = meter.createCounter("activitypub.outbox.activity", {
|
|
129
|
+
description: "ActivityPub activities observed at the outbox lifecycle level: queued, retried, or abandoned. Per-recipient delivery counters live on `activitypub.delivery.*`.",
|
|
130
|
+
unit: "{activity}"
|
|
131
|
+
});
|
|
109
132
|
}
|
|
110
133
|
recordDelivery(inbox, durationMs, success, activityType) {
|
|
111
134
|
const deliveryAttributes = {
|
|
@@ -127,6 +150,23 @@ var FederationMetrics = class {
|
|
|
127
150
|
if (remoteHost != null) attributes["activitypub.remote.host"] = remoteHost;
|
|
128
151
|
this.signatureVerificationFailure.add(1, attributes);
|
|
129
152
|
}
|
|
153
|
+
recordSignatureVerificationDuration(durationMs, kind, result, extra = {}) {
|
|
154
|
+
const attributes = {
|
|
155
|
+
"activitypub.signature.kind": kind,
|
|
156
|
+
"activitypub.signature.result": result
|
|
157
|
+
};
|
|
158
|
+
if (extra.algorithm != null) attributes["http_signatures.algorithm"] = extra.algorithm;
|
|
159
|
+
if (extra.failureReason != null) attributes["http_signatures.failure_reason"] = extra.failureReason;
|
|
160
|
+
if (extra.ldType != null) attributes["ld_signatures.type"] = extra.ldType;
|
|
161
|
+
if (extra.cryptosuite != null) attributes["object_integrity_proofs.cryptosuite"] = extra.cryptosuite;
|
|
162
|
+
this.signatureVerificationDuration.record(durationMs, attributes);
|
|
163
|
+
}
|
|
164
|
+
recordSignatureKeyFetchDuration(durationMs, kind, result) {
|
|
165
|
+
this.signatureKeyFetchDuration.record(durationMs, {
|
|
166
|
+
"activitypub.signature.kind": kind,
|
|
167
|
+
"activitypub.signature.key_fetch.result": result
|
|
168
|
+
});
|
|
169
|
+
}
|
|
130
170
|
recordInboxProcessingDuration(activityType, durationMs) {
|
|
131
171
|
this.inboxProcessingDuration.record(durationMs, { "activitypub.activity.type": activityType });
|
|
132
172
|
}
|
|
@@ -161,7 +201,23 @@ var FederationMetrics = class {
|
|
|
161
201
|
else if (result === "failed") this.queueTaskFailed.add(1, attributes);
|
|
162
202
|
this.queueTaskDuration.record(durationMs, attributes);
|
|
163
203
|
}
|
|
204
|
+
recordFanoutRecipients(recipientCount, activityType) {
|
|
205
|
+
const attributes = {};
|
|
206
|
+
if (activityType != null) attributes["activitypub.activity.type"] = activityType;
|
|
207
|
+
this.fanoutRecipients.record(recipientCount, attributes);
|
|
208
|
+
}
|
|
209
|
+
recordInboxActivity(result, activityType) {
|
|
210
|
+
this.inboxActivity.add(1, buildActivityLifecycleAttributes(result, activityType));
|
|
211
|
+
}
|
|
212
|
+
recordOutboxActivity(result, activityType) {
|
|
213
|
+
this.outboxActivity.add(1, buildActivityLifecycleAttributes(result, activityType));
|
|
214
|
+
}
|
|
164
215
|
};
|
|
216
|
+
function buildActivityLifecycleAttributes(result, activityType) {
|
|
217
|
+
const attributes = { "activitypub.processing.result": result };
|
|
218
|
+
if (activityType != null) attributes["activitypub.activity.type"] = activityType;
|
|
219
|
+
return attributes;
|
|
220
|
+
}
|
|
165
221
|
function buildQueueTaskAttributes(common) {
|
|
166
222
|
const attributes = { "fedify.queue.role": common.role };
|
|
167
223
|
const backend = getQueueBackend(common.queue);
|
|
@@ -191,20 +247,80 @@ function getQueueBackend(queue) {
|
|
|
191
247
|
return name;
|
|
192
248
|
}
|
|
193
249
|
/**
|
|
194
|
-
* Records `fedify.queue.task.enqueued` for an outgoing outbox enqueue
|
|
250
|
+
* Records `fedify.queue.task.enqueued` for an outgoing outbox enqueue and,
|
|
251
|
+
* for the initial attempt, also records
|
|
252
|
+
* `activitypub.outbox.activity{queued}`.
|
|
195
253
|
*
|
|
196
254
|
* Both `Context.sendActivity()` and `OutboxContext.forwardActivity()` enqueue
|
|
197
255
|
* outbox messages with the same metric attributes (role, queue, activity
|
|
198
256
|
* type, attempt), so they share this helper rather than each defining a local
|
|
199
|
-
* closure.
|
|
257
|
+
* closure. Retry enqueues (attempt > 0) intentionally do not record a
|
|
258
|
+
* second `activitypub.outbox.activity{queued}`; retries are reported as
|
|
259
|
+
* `result=retried` from the retry-scheduling site, which has the failure
|
|
260
|
+
* context.
|
|
200
261
|
* @since 2.3.0
|
|
201
262
|
*/
|
|
202
263
|
function recordOutboxEnqueue(meterProvider, outboxQueue, message) {
|
|
203
|
-
getFederationMetrics(meterProvider)
|
|
264
|
+
const metrics = getFederationMetrics(meterProvider);
|
|
265
|
+
metrics.recordQueueTaskEnqueued({
|
|
204
266
|
role: "outbox",
|
|
205
267
|
queue: outboxQueue,
|
|
206
268
|
activityType: message.activityType
|
|
207
269
|
}, message.attempt);
|
|
270
|
+
if (message.attempt === 0) metrics.recordOutboxActivity("queued", message.activityType);
|
|
271
|
+
}
|
|
272
|
+
/**
|
|
273
|
+
* Records `activitypub.fanout.recipients` with the number of recipient
|
|
274
|
+
* inboxes a single fanout produced. The histogram is unitless count
|
|
275
|
+
* (one measurement per fanout enqueue). Recipient URLs are deliberately
|
|
276
|
+
* not recorded; only the activity type, when known.
|
|
277
|
+
* @since 2.3.0
|
|
278
|
+
*/
|
|
279
|
+
function recordFanoutRecipients(meterProvider, recipientCount, activityType) {
|
|
280
|
+
getFederationMetrics(meterProvider).recordFanoutRecipients(recipientCount, activityType);
|
|
281
|
+
}
|
|
282
|
+
/**
|
|
283
|
+
* Records one `activitypub.inbox.activity` measurement. The
|
|
284
|
+
* `activitypub.processing.result` attribute is always present;
|
|
285
|
+
* `activitypub.activity.type` is recorded only when Fedify already knows
|
|
286
|
+
* the activity type.
|
|
287
|
+
* @since 2.3.0
|
|
288
|
+
*/
|
|
289
|
+
function recordInboxActivity(meterProvider, result, activityType) {
|
|
290
|
+
getFederationMetrics(meterProvider).recordInboxActivity(result, activityType);
|
|
291
|
+
}
|
|
292
|
+
/**
|
|
293
|
+
* Records one `activitypub.outbox.activity` measurement. The
|
|
294
|
+
* `activitypub.processing.result` attribute is always present;
|
|
295
|
+
* `activitypub.activity.type` is recorded only when Fedify already knows
|
|
296
|
+
* the activity type (it is always known for outbox lifecycle events).
|
|
297
|
+
* @since 2.3.0
|
|
298
|
+
*/
|
|
299
|
+
function recordOutboxActivity(meterProvider, result, activityType) {
|
|
300
|
+
getFederationMetrics(meterProvider).recordOutboxActivity(result, activityType);
|
|
301
|
+
}
|
|
302
|
+
/**
|
|
303
|
+
* Times an awaited public key fetch and records exactly one
|
|
304
|
+
* `activitypub.signature.key_fetch.duration` measurement, classifying the
|
|
305
|
+
* outcome as `hit`, `fetched`, or `error` based on the `cached` flag and
|
|
306
|
+
* whether the returned key is non-null. Errors thrown by the fetch are
|
|
307
|
+
* reported as `error` and rethrown, so verifier behavior is unchanged.
|
|
308
|
+
*
|
|
309
|
+
* Shared by the three signature verifiers (HTTP, Linked Data, Object
|
|
310
|
+
* Integrity Proofs); the only per-call variation is the
|
|
311
|
+
* `activitypub.signature.kind` attribute value.
|
|
312
|
+
* @since 2.3.0
|
|
313
|
+
*/
|
|
314
|
+
async function measureSignatureKeyFetch(meterProvider, kind, fetch) {
|
|
315
|
+
const start = performance.now();
|
|
316
|
+
try {
|
|
317
|
+
const result = await fetch();
|
|
318
|
+
getFederationMetrics(meterProvider).recordSignatureKeyFetchDuration(getDurationMs(start), kind, result.key != null ? result.cached ? "hit" : "fetched" : "error");
|
|
319
|
+
return result;
|
|
320
|
+
} catch (error) {
|
|
321
|
+
getFederationMetrics(meterProvider).recordSignatureKeyFetchDuration(getDurationMs(start), kind, "error");
|
|
322
|
+
throw error;
|
|
323
|
+
}
|
|
208
324
|
}
|
|
209
325
|
/**
|
|
210
326
|
* Whether the given thrown value is an `AbortError`.
|
|
@@ -263,220 +379,4 @@ function getDurationMs(start) {
|
|
|
263
379
|
return Math.max(0, performance.now() - start);
|
|
264
380
|
}
|
|
265
381
|
//#endregion
|
|
266
|
-
|
|
267
|
-
/**
|
|
268
|
-
* Extracts the inbox URLs from recipients.
|
|
269
|
-
* @param parameters The parameters to extract the inboxes.
|
|
270
|
-
* See also {@link ExtractInboxesParameters}.
|
|
271
|
-
* @returns The inboxes as a map of inbox URL to actor URIs.
|
|
272
|
-
*/
|
|
273
|
-
function extractInboxes({ recipients, preferSharedInbox, excludeBaseUris }) {
|
|
274
|
-
const inboxes = {};
|
|
275
|
-
for (const recipient of recipients) {
|
|
276
|
-
let inbox;
|
|
277
|
-
let sharedInbox = false;
|
|
278
|
-
if (preferSharedInbox && recipient.endpoints?.sharedInbox != null) {
|
|
279
|
-
inbox = recipient.endpoints.sharedInbox;
|
|
280
|
-
sharedInbox = true;
|
|
281
|
-
} else inbox = recipient.inboxId;
|
|
282
|
-
if (inbox != null && recipient.id != null) {
|
|
283
|
-
if (excludeBaseUris != null && excludeBaseUris.some((u) => u.origin === inbox?.origin)) continue;
|
|
284
|
-
inboxes[inbox.href] ??= {
|
|
285
|
-
actorIds: /* @__PURE__ */ new Set(),
|
|
286
|
-
sharedInbox
|
|
287
|
-
};
|
|
288
|
-
inboxes[inbox.href].actorIds.add(recipient.id.href);
|
|
289
|
-
}
|
|
290
|
-
}
|
|
291
|
-
return inboxes;
|
|
292
|
-
}
|
|
293
|
-
/**
|
|
294
|
-
* Sends an {@link Activity} to an inbox.
|
|
295
|
-
*
|
|
296
|
-
* @param parameters The parameters for sending the activity.
|
|
297
|
-
* See also {@link SendActivityParameters}.
|
|
298
|
-
* @throws {Error} If the activity fails to send.
|
|
299
|
-
*/
|
|
300
|
-
function sendActivity(options) {
|
|
301
|
-
const tracerProvider = options.tracerProvider ?? trace.getTracerProvider();
|
|
302
|
-
return tracerProvider.getTracer(name, version).startActiveSpan("activitypub.send_activity", {
|
|
303
|
-
kind: SpanKind.CLIENT,
|
|
304
|
-
attributes: { "activitypub.shared_inbox": options.sharedInbox ?? false }
|
|
305
|
-
}, async (span) => {
|
|
306
|
-
if (options.activityId != null) span.setAttribute("activitypub.activity.id", options.activityId);
|
|
307
|
-
if (options.activityType != null) span.setAttribute("activitypub.activity.type", options.activityType);
|
|
308
|
-
try {
|
|
309
|
-
await sendActivityInternal({
|
|
310
|
-
...options,
|
|
311
|
-
tracerProvider
|
|
312
|
-
}, span);
|
|
313
|
-
} catch (e) {
|
|
314
|
-
span.setStatus({
|
|
315
|
-
code: SpanStatusCode.ERROR,
|
|
316
|
-
message: String(e)
|
|
317
|
-
});
|
|
318
|
-
throw e;
|
|
319
|
-
} finally {
|
|
320
|
-
span.end();
|
|
321
|
-
}
|
|
322
|
-
});
|
|
323
|
-
}
|
|
324
|
-
const MAX_ERROR_RESPONSE_BODY_BYTES = 1024;
|
|
325
|
-
function getActivityActorId(activity) {
|
|
326
|
-
if (!isRecord(activity)) return void 0;
|
|
327
|
-
return getIdValue(activity.actor);
|
|
328
|
-
}
|
|
329
|
-
function getIdValue(value) {
|
|
330
|
-
if (typeof value === "string" && value !== "") return value;
|
|
331
|
-
if (value instanceof URL) return value.href;
|
|
332
|
-
if (Array.isArray(value)) {
|
|
333
|
-
for (const item of value) {
|
|
334
|
-
const id = getIdValue(item);
|
|
335
|
-
if (id != null) return id;
|
|
336
|
-
}
|
|
337
|
-
return;
|
|
338
|
-
}
|
|
339
|
-
if (isRecord(value)) return getIdValue(value.id);
|
|
340
|
-
}
|
|
341
|
-
function isRecord(value) {
|
|
342
|
-
return typeof value === "object" && value != null;
|
|
343
|
-
}
|
|
344
|
-
async function readLimitedResponseBody(response, maxBytes) {
|
|
345
|
-
if (response.body == null) return "";
|
|
346
|
-
const reader = response.body.getReader();
|
|
347
|
-
const decoder = new TextDecoder();
|
|
348
|
-
const chunks = [];
|
|
349
|
-
let totalBytes = 0;
|
|
350
|
-
let truncated = false;
|
|
351
|
-
try {
|
|
352
|
-
while (true) {
|
|
353
|
-
const { done, value } = await reader.read();
|
|
354
|
-
if (done) break;
|
|
355
|
-
if (totalBytes + value.length > maxBytes) {
|
|
356
|
-
const remaining = maxBytes - totalBytes;
|
|
357
|
-
if (remaining > 0) chunks.push(decoder.decode(value.slice(0, remaining), { stream: true }));
|
|
358
|
-
truncated = true;
|
|
359
|
-
break;
|
|
360
|
-
}
|
|
361
|
-
chunks.push(decoder.decode(value, { stream: true }));
|
|
362
|
-
totalBytes += value.length;
|
|
363
|
-
}
|
|
364
|
-
} finally {
|
|
365
|
-
reader.releaseLock();
|
|
366
|
-
}
|
|
367
|
-
let result = chunks.join("");
|
|
368
|
-
if (truncated) result += "… (truncated)";
|
|
369
|
-
return result;
|
|
370
|
-
}
|
|
371
|
-
async function sendActivityInternal({ activity, activityId, activityType, keys, inbox, headers, specDeterminer, meterProvider, tracerProvider }, span) {
|
|
372
|
-
const logger = getLogger([
|
|
373
|
-
"fedify",
|
|
374
|
-
"federation",
|
|
375
|
-
"outbox"
|
|
376
|
-
]);
|
|
377
|
-
const federationMetrics = getFederationMetrics(meterProvider);
|
|
378
|
-
const started = performance.now();
|
|
379
|
-
let deliverySuccess = false;
|
|
380
|
-
headers = new Headers(headers);
|
|
381
|
-
headers.set("Content-Type", "application/activity+json");
|
|
382
|
-
const request = new Request(inbox, {
|
|
383
|
-
method: "POST",
|
|
384
|
-
headers,
|
|
385
|
-
body: JSON.stringify(activity)
|
|
386
|
-
});
|
|
387
|
-
let rsaKey = null;
|
|
388
|
-
for (const key of keys) if (key.privateKey.algorithm.name === "RSASSA-PKCS1-v1_5") {
|
|
389
|
-
rsaKey = key;
|
|
390
|
-
break;
|
|
391
|
-
}
|
|
392
|
-
if (rsaKey == null) logger.warn("No supported key found to sign the request to {inbox}. The request will be sent without a signature. In order to sign the request, at least one RSASSA-PKCS1-v1_5 key must be provided.", {
|
|
393
|
-
inbox: inbox.href,
|
|
394
|
-
keys: keys.map((pair) => ({
|
|
395
|
-
keyId: pair.keyId.href,
|
|
396
|
-
privateKey: pair.privateKey
|
|
397
|
-
}))
|
|
398
|
-
});
|
|
399
|
-
let response;
|
|
400
|
-
try {
|
|
401
|
-
response = rsaKey == null ? await fetch(request) : await doubleKnock(request, rsaKey, {
|
|
402
|
-
tracerProvider,
|
|
403
|
-
specDeterminer
|
|
404
|
-
});
|
|
405
|
-
} catch (error) {
|
|
406
|
-
logger.error("Failed to send activity {activityId} to {inbox}:\n{error}", {
|
|
407
|
-
activityId,
|
|
408
|
-
inbox: inbox.href,
|
|
409
|
-
error
|
|
410
|
-
});
|
|
411
|
-
federationMetrics.recordDelivery(inbox, getDurationMs(started), false, activityType);
|
|
412
|
-
throw error;
|
|
413
|
-
}
|
|
414
|
-
try {
|
|
415
|
-
if (!response.ok) {
|
|
416
|
-
let error;
|
|
417
|
-
try {
|
|
418
|
-
error = await readLimitedResponseBody(response, MAX_ERROR_RESPONSE_BODY_BYTES);
|
|
419
|
-
} catch (_) {
|
|
420
|
-
error = "";
|
|
421
|
-
}
|
|
422
|
-
logger.error("Failed to send activity {activityId} to {inbox} ({status} {statusText}):\n{error}", {
|
|
423
|
-
activityId,
|
|
424
|
-
inbox: inbox.href,
|
|
425
|
-
status: response.status,
|
|
426
|
-
statusText: response.statusText,
|
|
427
|
-
error
|
|
428
|
-
});
|
|
429
|
-
throw new SendActivityError(inbox, response.status, `Failed to send activity ${activityId} to ${inbox.href} (${response.status} ${response.statusText}):\n${error}`, error);
|
|
430
|
-
}
|
|
431
|
-
deliverySuccess = true;
|
|
432
|
-
const eventAttributes = {
|
|
433
|
-
"activitypub.inbox.url": inbox.href,
|
|
434
|
-
"activitypub.activity.id": activityId ?? ""
|
|
435
|
-
};
|
|
436
|
-
if (activityType != null) eventAttributes["activitypub.activity.type"] = activityType;
|
|
437
|
-
const actorId = getActivityActorId(activity);
|
|
438
|
-
if (actorId != null) eventAttributes["activitypub.actor.id"] = actorId;
|
|
439
|
-
span.addEvent("activitypub.activity.sent", eventAttributes);
|
|
440
|
-
} finally {
|
|
441
|
-
federationMetrics.recordDelivery(inbox, getDurationMs(started), deliverySuccess, activityType);
|
|
442
|
-
}
|
|
443
|
-
}
|
|
444
|
-
/**
|
|
445
|
-
* An error that is thrown when an activity fails to send to a remote inbox.
|
|
446
|
-
* It contains structured information about the failure, including the HTTP
|
|
447
|
-
* status code, the inbox URL, and the response body.
|
|
448
|
-
* @since 2.0.0
|
|
449
|
-
*/
|
|
450
|
-
var SendActivityError = class extends Error {
|
|
451
|
-
/**
|
|
452
|
-
* The inbox URL that the activity was being sent to.
|
|
453
|
-
*/
|
|
454
|
-
inbox;
|
|
455
|
-
/**
|
|
456
|
-
* The HTTP status code returned by the inbox.
|
|
457
|
-
*/
|
|
458
|
-
statusCode;
|
|
459
|
-
/**
|
|
460
|
-
* The response body from the inbox, if any. Note that this may be
|
|
461
|
-
* truncated to a maximum of 1 KiB to prevent excessive memory consumption
|
|
462
|
-
* when remote servers return large error pages (e.g., Cloudflare error pages).
|
|
463
|
-
* If truncated, the string will end with `"… (truncated)"`.
|
|
464
|
-
*/
|
|
465
|
-
responseBody;
|
|
466
|
-
/**
|
|
467
|
-
* Creates a new {@link SendActivityError}.
|
|
468
|
-
* @param inbox The inbox URL.
|
|
469
|
-
* @param statusCode The HTTP status code.
|
|
470
|
-
* @param message The error message.
|
|
471
|
-
* @param responseBody The response body.
|
|
472
|
-
*/
|
|
473
|
-
constructor(inbox, statusCode, message, responseBody) {
|
|
474
|
-
super(message);
|
|
475
|
-
this.name = "SendActivityError";
|
|
476
|
-
this.inbox = inbox;
|
|
477
|
-
this.statusCode = statusCode;
|
|
478
|
-
this.responseBody = responseBody;
|
|
479
|
-
}
|
|
480
|
-
};
|
|
481
|
-
//#endregion
|
|
482
|
-
export { getFederationMetrics as a, recordOutboxEnqueue as c, getDurationMs as i, extractInboxes as n, getRemoteHost as o, sendActivity as r, isAbortError as s, SendActivityError as t };
|
|
382
|
+
export { measureSignatureKeyFetch as a, recordOutboxActivity as c, isAbortError as i, recordOutboxEnqueue as l, getFederationMetrics as n, recordFanoutRecipients as o, getRemoteHost as r, recordInboxActivity as s, getDurationMs as t };
|