@fedify/fedify 2.0.0-dev.1961 → 2.0.0-dev.85
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/README.md +3 -0
- package/dist/{actor-DhgrrgXz.cjs → actor-Bpgsv6Ii.cjs} +185 -185
- package/dist/{actor-DiKHxw_H.js → actor-D3v8T9kQ.js} +185 -185
- package/dist/{actor-Kqyoic-M.js → actor-DrvDW-tK.js} +1 -1
- package/dist/{builder-BrqBKkVi.js → builder-D2trc6p7.js} +3 -3
- package/dist/compat/mod.d.cts +3 -3
- package/dist/compat/mod.d.ts +3 -3
- package/dist/compat/transformers.test.js +16 -16
- package/dist/{context-PxGADCsD.d.cts → context-DBQ7nMCw.d.cts} +1 -1
- package/dist/{context-V-XS2_6O.d.ts → context-mM0Nu5yo.d.ts} +1 -1
- package/dist/{docloader-HB61Uc88.js → docloader-DlSrn4Io.js} +2 -2
- package/dist/{esm-DaZiDvFW.js → esm-DQKBkSAc.js} +1 -1
- package/dist/federation/builder.test.js +5 -5
- package/dist/federation/collection.test.js +2 -2
- package/dist/federation/handler.test.js +104 -17
- package/dist/federation/idempotency.test.js +17 -17
- package/dist/federation/inbox.test.js +3 -3
- package/dist/federation/keycache.test.js +4 -4
- package/dist/federation/kv.test.js +57 -3
- package/dist/federation/middleware.test.js +18 -18
- package/dist/federation/mod.cjs +8 -8
- package/dist/federation/mod.d.cts +3 -3
- package/dist/federation/mod.d.ts +3 -3
- package/dist/federation/mod.js +8 -8
- package/dist/federation/mq.test.js +2 -2
- package/dist/federation/negotiation.test.js +2 -2
- package/dist/federation/retry.test.js +2 -2
- package/dist/federation/router.test.js +2 -2
- package/dist/federation/send.test.js +52 -9
- package/dist/{federation-CRpdnOMS.cjs → federation-CoW-KDKv.cjs} +22 -0
- package/dist/{federation-jcR8-ZxP.js → federation-D0hkM4T7.js} +22 -0
- package/dist/{http-B2bao4EB.js → http-BNxhNjYm.js} +2 -2
- package/dist/{http-CUsGQiwU.js → http-Bz4YpW-D.js} +2 -2
- package/dist/{http-BxgYWxTY.cjs → http-Dr7V4DY7.cjs} +2 -2
- package/dist/{inbox-CAojlSWc.js → inbox-CXOoDyEH.js} +1 -1
- package/dist/{key-Dz-KWCap.js → key-C_b1t1Na.js} +2 -2
- package/dist/{keycache-Czcf33al.js → keycache-zfG6F1PW.js} +1 -1
- package/dist/{keys-BN4nelft.js → keys-C01rMN22.js} +1 -1
- package/dist/{kv-BKNZ-Tb-.d.ts → kv-BpJND1Hr.d.ts} +30 -1
- package/dist/{kv-CRZrzyXm.js → kv-QzKcOQgP.js} +22 -0
- package/dist/{kv-Bxr0Q87_.d.cts → kv-cD_d4hg-.d.cts} +30 -1
- package/dist/{kv-cache-Bq6kUUoG.js → kv-cache-4I_O-pli.js} +1 -1
- package/dist/{kv-cache-5j5Pb-V6.cjs → kv-cache-CpjdzlKh.cjs} +1 -1
- package/dist/{kv-cache-DN9pfMBe.js → kv-cache-D7LFlByV.js} +13 -0
- package/dist/{ld-CEJHYq8u.js → ld-Ba0jIf7k.js} +2 -2
- package/dist/{lookup-gMu_9ZKY.js → lookup-BUqdDUac.js} +6 -5
- package/dist/{lookup-_Hap2IXS.cjs → lookup-UA4SKewV.cjs} +6 -5
- package/dist/{lookup-D8hvtZHY.js → lookup-kl47RWf3.js} +195 -189
- package/dist/middleware-B64_UxVQ.js +26 -0
- package/dist/middleware-BK7fptEJ.cjs +15 -0
- package/dist/{middleware-DW3rJHWy.cjs → middleware-BYqpBCFp.cjs} +21 -9
- package/dist/{middleware-DgrceAHB.js → middleware-CwpDL0KB.js} +19 -12
- package/dist/{middleware-Df9OWALM.js → middleware-Cx0KwuzQ.js} +21 -9
- package/dist/middleware-_DVIaAFk.js +15 -0
- package/dist/{mod-aAE2wOWV.d.ts → mod-CAdoBu0x.d.ts} +1 -1
- package/dist/{mod-BoRKfJPE.d.cts → mod-Dy2fJtmN.d.cts} +1 -1
- package/dist/{mod-Cdo6SYlJ.d.ts → mod-GetHzY6F.d.ts} +1 -1
- package/dist/{mod-DMpuiKXi.d.cts → mod-fxr25Gv7.d.cts} +1 -1
- package/dist/mod.cjs +8 -8
- package/dist/mod.d.cts +5 -5
- package/dist/mod.d.ts +5 -5
- package/dist/mod.js +8 -8
- package/dist/nodeinfo/client.test.js +3 -3
- package/dist/nodeinfo/handler.test.js +16 -16
- package/dist/nodeinfo/types.test.js +2 -2
- package/dist/otel/exporter.test.d.ts +3 -0
- package/dist/otel/exporter.test.js +900 -0
- package/dist/otel/mod.cjs +262 -0
- package/dist/otel/mod.d.cts +230 -0
- package/dist/otel/mod.d.ts +232 -0
- package/dist/otel/mod.js +261 -0
- package/dist/otel-Bfe_BgOV.js +63 -0
- package/dist/{owner-JwI-WzF-.js → owner-CgznkTZ6.js} +44 -8
- package/dist/{proof-CObJuclI.cjs → proof-AEFUz3SS.cjs} +44 -8
- package/dist/{proof-xH0V1mdD.js → proof-BrsOl9bB.js} +45 -9
- package/dist/{proof-Dhqndmfg.js → proof-rFeNfcVE.js} +2 -2
- package/dist/{send-1o7D-GGL.js → send-w_IyjZ0S.js} +9 -4
- package/dist/sig/http.test.js +7 -7
- package/dist/sig/key.test.js +5 -5
- package/dist/sig/ld.test.js +6 -6
- package/dist/sig/mod.cjs +4 -4
- package/dist/sig/mod.js +4 -4
- package/dist/sig/owner.test.js +30 -6
- package/dist/sig/proof.test.js +6 -6
- package/dist/testing/docloader.test.js +2 -2
- package/dist/testing/mod.js +2 -2
- package/dist/{testing-DpRy1YTU.js → testing-fIO3Z258.js} +1 -1
- package/dist/utils/docloader.test.js +8 -8
- package/dist/utils/kv-cache.test.js +6 -3
- package/dist/utils/mod.cjs +4 -4
- package/dist/utils/mod.d.cts +2 -2
- package/dist/utils/mod.d.ts +2 -2
- package/dist/utils/mod.js +4 -4
- package/dist/vocab/actor.test.js +4 -4
- package/dist/vocab/lookup.test.js +26 -4
- package/dist/vocab/mod.cjs +3 -3
- package/dist/vocab/mod.js +3 -3
- package/dist/vocab/type.test.js +2 -2
- package/dist/vocab/vocab.test.js +3 -3
- package/dist/{vocab-CAwj263k.js → vocab-BtqMcZvq.js} +7 -2
- package/dist/{vocab-DgHGCFcw.cjs → vocab-COVHviQ5.cjs} +7 -2
- package/dist/webfinger/handler.test.js +16 -16
- package/dist/webfinger/lookup.test.js +3 -3
- package/dist/webfinger/mod.cjs +1 -1
- package/dist/webfinger/mod.js +1 -1
- package/package.json +19 -9
- package/dist/middleware-19QFZj7b.js +0 -26
- package/dist/middleware-BbbcXY4w.cjs +0 -15
- package/dist/middleware-CSTK543z.js +0 -15
|
@@ -0,0 +1,900 @@
|
|
|
1
|
+
|
|
2
|
+
import { Temporal } from "@js-temporal/polyfill";
|
|
3
|
+
import { URLPattern } from "urlpattern-polyfill";
|
|
4
|
+
globalThis.addEventListener = () => {};
|
|
5
|
+
|
|
6
|
+
import "../lookup-kl47RWf3.js";
|
|
7
|
+
import { assertEquals } from "../assert_equals-DSbWqCm3.js";
|
|
8
|
+
import "../assert-MZs1qjMx.js";
|
|
9
|
+
import "../assert_instance_of-DHz7EHNU.js";
|
|
10
|
+
import { MemoryKvStore } from "../kv-QzKcOQgP.js";
|
|
11
|
+
import { test } from "../testing-fIO3Z258.js";
|
|
12
|
+
import "../std__assert-DWivtrGR.js";
|
|
13
|
+
import "../assert_rejects-Ce45JcFg.js";
|
|
14
|
+
import "../assert_throws-BNXdRGWP.js";
|
|
15
|
+
import "../assert_not_equals-C80BG-_5.js";
|
|
16
|
+
import { getLogger } from "@logtape/logtape";
|
|
17
|
+
import { SpanKind, SpanStatusCode, TraceFlags } from "@opentelemetry/api";
|
|
18
|
+
import { ExportResultCode } from "@opentelemetry/core";
|
|
19
|
+
|
|
20
|
+
//#region src/otel/exporter.ts
|
|
21
|
+
/**
|
|
22
|
+
* A SpanExporter that persists ActivityPub activity traces to a
|
|
23
|
+
* {@link KvStore}. This enables distributed tracing across multiple
|
|
24
|
+
* nodes in a Fedify deployment.
|
|
25
|
+
*
|
|
26
|
+
* The exporter captures activity data from OpenTelemetry span events
|
|
27
|
+
* (`activitypub.activity.received` and `activitypub.activity.sent`)
|
|
28
|
+
* and stores them in the KvStore with trace context preserved.
|
|
29
|
+
*
|
|
30
|
+
* @example Basic usage with MemoryKvStore
|
|
31
|
+
* ```typescript ignore
|
|
32
|
+
* import { MemoryKvStore } from "@fedify/fedify";
|
|
33
|
+
* import { FedifySpanExporter } from "@fedify/fedify/otel";
|
|
34
|
+
* import {
|
|
35
|
+
* BasicTracerProvider,
|
|
36
|
+
* SimpleSpanProcessor,
|
|
37
|
+
* } from "@opentelemetry/sdk-trace-base";
|
|
38
|
+
*
|
|
39
|
+
* const kv = new MemoryKvStore();
|
|
40
|
+
* const exporter = new FedifySpanExporter(kv, {
|
|
41
|
+
* ttl: Temporal.Duration.from({ hours: 1 }),
|
|
42
|
+
* });
|
|
43
|
+
*
|
|
44
|
+
* const provider = new BasicTracerProvider({
|
|
45
|
+
* spanProcessors: [new SimpleSpanProcessor(exporter)],
|
|
46
|
+
* });
|
|
47
|
+
* ```
|
|
48
|
+
*
|
|
49
|
+
* @example Querying stored traces
|
|
50
|
+
* ```typescript ignore
|
|
51
|
+
* import { MemoryKvStore } from "@fedify/fedify";
|
|
52
|
+
* import { FedifySpanExporter } from "@fedify/fedify/otel";
|
|
53
|
+
*
|
|
54
|
+
* const kv = new MemoryKvStore();
|
|
55
|
+
* const exporter = new FedifySpanExporter(kv);
|
|
56
|
+
* const traceId = "abc123";
|
|
57
|
+
*
|
|
58
|
+
* // Get all activities for a specific trace
|
|
59
|
+
* const activities = await exporter.getActivitiesByTraceId(traceId);
|
|
60
|
+
*
|
|
61
|
+
* // Get recent traces
|
|
62
|
+
* const recentTraces = await exporter.getRecentTraces({ limit: 100 });
|
|
63
|
+
* ```
|
|
64
|
+
*
|
|
65
|
+
* @since 1.10.0
|
|
66
|
+
*/
|
|
67
|
+
var FedifySpanExporter = class {
|
|
68
|
+
#kv;
|
|
69
|
+
#ttl;
|
|
70
|
+
#keyPrefix;
|
|
71
|
+
/**
|
|
72
|
+
* Creates a new FedifySpanExporter.
|
|
73
|
+
*
|
|
74
|
+
* @param kv The KvStore to persist trace data to.
|
|
75
|
+
* @param options Configuration options.
|
|
76
|
+
*/
|
|
77
|
+
constructor(kv, options) {
|
|
78
|
+
this.#kv = kv;
|
|
79
|
+
this.#ttl = options?.ttl;
|
|
80
|
+
this.#keyPrefix = options?.keyPrefix ?? ["fedify", "traces"];
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Exports spans to the KvStore.
|
|
84
|
+
*
|
|
85
|
+
* @param spans The spans to export.
|
|
86
|
+
* @param resultCallback Callback to invoke with the export result.
|
|
87
|
+
*/
|
|
88
|
+
export(spans, resultCallback) {
|
|
89
|
+
this.#exportAsync(spans).then(() => resultCallback({ code: ExportResultCode.SUCCESS })).catch((error) => {
|
|
90
|
+
getLogger([
|
|
91
|
+
"fedify",
|
|
92
|
+
"otel",
|
|
93
|
+
"exporter"
|
|
94
|
+
]).error("Failed to export spans to KvStore: {error}", { error });
|
|
95
|
+
resultCallback({ code: ExportResultCode.FAILED });
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
async #exportAsync(spans) {
|
|
99
|
+
const storeOperations = [];
|
|
100
|
+
for (const span of spans) {
|
|
101
|
+
const records = this.#extractRecords(span);
|
|
102
|
+
for (const record of records) storeOperations.push(this.#storeRecord(record));
|
|
103
|
+
}
|
|
104
|
+
const results = await Promise.allSettled(storeOperations);
|
|
105
|
+
const rejected = results.filter((r) => r.status === "rejected");
|
|
106
|
+
if (rejected.length > 0) throw new AggregateError(rejected.map((r) => r.reason), "Failed to store one or more trace activity records.");
|
|
107
|
+
}
|
|
108
|
+
#extractRecords(span) {
|
|
109
|
+
const records = [];
|
|
110
|
+
const spanContext = span.spanContext();
|
|
111
|
+
const traceId = spanContext.traceId;
|
|
112
|
+
const spanId = spanContext.spanId;
|
|
113
|
+
const parentSpanId = span.parentSpanContext?.spanId;
|
|
114
|
+
for (const event of span.events) if (event.name === "activitypub.activity.received") {
|
|
115
|
+
const record = this.#extractInboundRecord(event, traceId, spanId, parentSpanId);
|
|
116
|
+
if (record != null) records.push(record);
|
|
117
|
+
} else if (event.name === "activitypub.activity.sent") {
|
|
118
|
+
const record = this.#extractOutboundRecord(event, traceId, spanId, parentSpanId);
|
|
119
|
+
if (record != null) records.push(record);
|
|
120
|
+
}
|
|
121
|
+
return records;
|
|
122
|
+
}
|
|
123
|
+
#extractInboundRecord(event, traceId, spanId, parentSpanId) {
|
|
124
|
+
const attrs = event.attributes;
|
|
125
|
+
if (attrs == null) return null;
|
|
126
|
+
const activityJson = attrs["activitypub.activity.json"];
|
|
127
|
+
if (typeof activityJson !== "string") return null;
|
|
128
|
+
let activityType = "Unknown";
|
|
129
|
+
let activityId;
|
|
130
|
+
let actorId;
|
|
131
|
+
try {
|
|
132
|
+
const activity = JSON.parse(activityJson);
|
|
133
|
+
activityType = activity.type ?? "Unknown";
|
|
134
|
+
activityId = activity.id;
|
|
135
|
+
if (typeof activity.actor === "string") actorId = activity.actor;
|
|
136
|
+
else if (activity.actor != null && typeof activity.actor.id === "string") actorId = activity.actor.id;
|
|
137
|
+
} catch {}
|
|
138
|
+
const verified = attrs["activitypub.activity.verified"];
|
|
139
|
+
const httpSigVerified = attrs["http_signatures.verified"];
|
|
140
|
+
const httpSigKeyId = attrs["http_signatures.key_id"];
|
|
141
|
+
const ldSigVerified = attrs["ld_signatures.verified"];
|
|
142
|
+
let signatureDetails;
|
|
143
|
+
if (typeof httpSigVerified === "boolean" || typeof ldSigVerified === "boolean") signatureDetails = {
|
|
144
|
+
httpSignaturesVerified: httpSigVerified === true,
|
|
145
|
+
httpSignaturesKeyId: typeof httpSigKeyId === "string" && httpSigKeyId !== "" ? httpSigKeyId : void 0,
|
|
146
|
+
ldSignaturesVerified: ldSigVerified === true
|
|
147
|
+
};
|
|
148
|
+
return {
|
|
149
|
+
traceId,
|
|
150
|
+
spanId,
|
|
151
|
+
parentSpanId,
|
|
152
|
+
direction: "inbound",
|
|
153
|
+
activityType,
|
|
154
|
+
activityId,
|
|
155
|
+
actorId,
|
|
156
|
+
activityJson,
|
|
157
|
+
verified: typeof verified === "boolean" ? verified : void 0,
|
|
158
|
+
signatureDetails,
|
|
159
|
+
timestamp: (/* @__PURE__ */ new Date(event.time[0] * 1e3 + event.time[1] / 1e6)).toISOString()
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
#extractOutboundRecord(event, traceId, spanId, parentSpanId) {
|
|
163
|
+
const attrs = event.attributes;
|
|
164
|
+
if (attrs == null) return null;
|
|
165
|
+
const activityJson = attrs["activitypub.activity.json"];
|
|
166
|
+
if (typeof activityJson !== "string") return null;
|
|
167
|
+
let activityType = "Unknown";
|
|
168
|
+
let activityId;
|
|
169
|
+
let actorId;
|
|
170
|
+
try {
|
|
171
|
+
const activity = JSON.parse(activityJson);
|
|
172
|
+
activityType = activity.type ?? "Unknown";
|
|
173
|
+
activityId = activity.id;
|
|
174
|
+
if (typeof activity.actor === "string") actorId = activity.actor;
|
|
175
|
+
else if (activity.actor != null && typeof activity.actor.id === "string") actorId = activity.actor.id;
|
|
176
|
+
} catch {}
|
|
177
|
+
const inboxUrl = attrs["activitypub.inbox.url"];
|
|
178
|
+
const explicitActivityId = attrs["activitypub.activity.id"];
|
|
179
|
+
return {
|
|
180
|
+
traceId,
|
|
181
|
+
spanId,
|
|
182
|
+
parentSpanId,
|
|
183
|
+
direction: "outbound",
|
|
184
|
+
activityType,
|
|
185
|
+
activityId: activityId ?? (typeof explicitActivityId === "string" && explicitActivityId !== "" ? explicitActivityId : void 0),
|
|
186
|
+
actorId,
|
|
187
|
+
activityJson,
|
|
188
|
+
timestamp: (/* @__PURE__ */ new Date(event.time[0] * 1e3 + event.time[1] / 1e6)).toISOString(),
|
|
189
|
+
inboxUrl: typeof inboxUrl === "string" ? inboxUrl : void 0
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
async #storeRecord(record) {
|
|
193
|
+
const options = this.#ttl != null ? { ttl: this.#ttl } : void 0;
|
|
194
|
+
const key = [
|
|
195
|
+
...this.#keyPrefix,
|
|
196
|
+
record.traceId,
|
|
197
|
+
record.spanId
|
|
198
|
+
];
|
|
199
|
+
await this.#kv.set(key, record, options);
|
|
200
|
+
await this.#updateTraceSummary(record, options);
|
|
201
|
+
}
|
|
202
|
+
async #setWithCasRetry(key, transform, options) {
|
|
203
|
+
if (this.#kv.cas != null) for (let attempt = 0; attempt < 3; attempt++) {
|
|
204
|
+
const existing$1 = await this.#kv.get(key);
|
|
205
|
+
const newValue$1 = transform(existing$1);
|
|
206
|
+
if (await this.#kv.cas(key, existing$1, newValue$1, options)) return;
|
|
207
|
+
}
|
|
208
|
+
const existing = await this.#kv.get(key);
|
|
209
|
+
const newValue = transform(existing);
|
|
210
|
+
await this.#kv.set(key, newValue, options);
|
|
211
|
+
}
|
|
212
|
+
async #updateTraceSummary(record, options) {
|
|
213
|
+
const summaryKey = [
|
|
214
|
+
...this.#keyPrefix,
|
|
215
|
+
"_summaries",
|
|
216
|
+
record.traceId
|
|
217
|
+
];
|
|
218
|
+
await this.#setWithCasRetry(summaryKey, (existing) => {
|
|
219
|
+
const summary = existing != null ? {
|
|
220
|
+
traceId: existing.traceId,
|
|
221
|
+
timestamp: existing.timestamp,
|
|
222
|
+
activityCount: existing.activityCount,
|
|
223
|
+
activityTypes: [...existing.activityTypes]
|
|
224
|
+
} : {
|
|
225
|
+
traceId: record.traceId,
|
|
226
|
+
timestamp: record.timestamp,
|
|
227
|
+
activityCount: 0,
|
|
228
|
+
activityTypes: []
|
|
229
|
+
};
|
|
230
|
+
summary.activityCount += 1;
|
|
231
|
+
if (!summary.activityTypes.includes(record.activityType)) summary.activityTypes.push(record.activityType);
|
|
232
|
+
return summary;
|
|
233
|
+
}, options);
|
|
234
|
+
}
|
|
235
|
+
/**
|
|
236
|
+
* Gets all activity records for a specific trace ID.
|
|
237
|
+
*
|
|
238
|
+
* @param traceId The trace ID to query.
|
|
239
|
+
* @returns An array of activity records belonging to the trace.
|
|
240
|
+
*/
|
|
241
|
+
async getActivitiesByTraceId(traceId) {
|
|
242
|
+
const prefix = [...this.#keyPrefix, traceId];
|
|
243
|
+
const records = [];
|
|
244
|
+
for await (const entry of this.#kv.list(prefix)) records.push(entry.value);
|
|
245
|
+
return records;
|
|
246
|
+
}
|
|
247
|
+
/**
|
|
248
|
+
* Gets recent traces with summary information.
|
|
249
|
+
*
|
|
250
|
+
* @param options Options for the query.
|
|
251
|
+
* @returns An array of trace summaries.
|
|
252
|
+
*/
|
|
253
|
+
async getRecentTraces(options) {
|
|
254
|
+
const summaryPrefix = [...this.#keyPrefix, "_summaries"];
|
|
255
|
+
const summaries = [];
|
|
256
|
+
for await (const entry of this.#kv.list(summaryPrefix)) summaries.push(entry.value);
|
|
257
|
+
summaries.sort((a, b) => b.timestamp.localeCompare(a.timestamp));
|
|
258
|
+
if (options?.limit != null) return summaries.slice(0, options.limit);
|
|
259
|
+
return summaries;
|
|
260
|
+
}
|
|
261
|
+
/**
|
|
262
|
+
* Forces the exporter to flush any buffered data.
|
|
263
|
+
* This is a no-op because we write directly to the KvStore without buffering.
|
|
264
|
+
*/
|
|
265
|
+
async forceFlush() {}
|
|
266
|
+
/**
|
|
267
|
+
* Shuts down the exporter.
|
|
268
|
+
*/
|
|
269
|
+
async shutdown() {}
|
|
270
|
+
};
|
|
271
|
+
|
|
272
|
+
//#endregion
|
|
273
|
+
//#region src/otel/exporter.test.ts
|
|
274
|
+
function createMockSpan(options) {
|
|
275
|
+
const traceId = options.traceId ?? "0123456789abcdef0123456789abcdef";
|
|
276
|
+
const spanId = options.spanId ?? "0123456789abcdef";
|
|
277
|
+
const spanContext = {
|
|
278
|
+
traceId,
|
|
279
|
+
spanId,
|
|
280
|
+
traceFlags: TraceFlags.SAMPLED
|
|
281
|
+
};
|
|
282
|
+
const parentSpanContext = options.parentSpanId ? {
|
|
283
|
+
traceId,
|
|
284
|
+
spanId: options.parentSpanId,
|
|
285
|
+
traceFlags: TraceFlags.SAMPLED
|
|
286
|
+
} : void 0;
|
|
287
|
+
return {
|
|
288
|
+
name: options.name ?? "test-span",
|
|
289
|
+
kind: SpanKind.INTERNAL,
|
|
290
|
+
spanContext: () => spanContext,
|
|
291
|
+
parentSpanContext,
|
|
292
|
+
startTime: [17e8, 0],
|
|
293
|
+
endTime: [1700000001, 0],
|
|
294
|
+
status: { code: SpanStatusCode.OK },
|
|
295
|
+
attributes: {},
|
|
296
|
+
links: [],
|
|
297
|
+
events: options.events ?? [],
|
|
298
|
+
duration: [1, 0],
|
|
299
|
+
ended: true,
|
|
300
|
+
resource: {
|
|
301
|
+
attributes: {},
|
|
302
|
+
getRawAttributes: () => [],
|
|
303
|
+
merge: () => ({
|
|
304
|
+
attributes: {},
|
|
305
|
+
getRawAttributes: () => [],
|
|
306
|
+
merge: () => null
|
|
307
|
+
})
|
|
308
|
+
},
|
|
309
|
+
instrumentationScope: { name: "test" },
|
|
310
|
+
droppedAttributesCount: 0,
|
|
311
|
+
droppedEventsCount: 0,
|
|
312
|
+
droppedLinksCount: 0
|
|
313
|
+
};
|
|
314
|
+
}
|
|
315
|
+
function createActivityReceivedEvent(options) {
|
|
316
|
+
return {
|
|
317
|
+
name: "activitypub.activity.received",
|
|
318
|
+
time: [17e8, 5e8],
|
|
319
|
+
attributes: {
|
|
320
|
+
"activitypub.activity.json": options.activityJson,
|
|
321
|
+
"activitypub.activity.verified": options.verified ?? true,
|
|
322
|
+
"ld_signatures.verified": options.ldSigVerified ?? false,
|
|
323
|
+
"http_signatures.verified": options.httpSigVerified ?? true,
|
|
324
|
+
"http_signatures.key_id": options.httpSigKeyId ?? ""
|
|
325
|
+
}
|
|
326
|
+
};
|
|
327
|
+
}
|
|
328
|
+
function createActivitySentEvent(options) {
|
|
329
|
+
return {
|
|
330
|
+
name: "activitypub.activity.sent",
|
|
331
|
+
time: [17e8, 5e8],
|
|
332
|
+
attributes: {
|
|
333
|
+
"activitypub.activity.json": options.activityJson,
|
|
334
|
+
"activitypub.inbox.url": options.inboxUrl,
|
|
335
|
+
"activitypub.activity.id": options.activityId ?? ""
|
|
336
|
+
}
|
|
337
|
+
};
|
|
338
|
+
}
|
|
339
|
+
test("FedifySpanExporter", async (t) => {
|
|
340
|
+
await t.step("constructor accepts KvStore with list()", () => {
|
|
341
|
+
const kv = new MemoryKvStore();
|
|
342
|
+
const exporter = new FedifySpanExporter(kv);
|
|
343
|
+
assertEquals(exporter instanceof FedifySpanExporter, true);
|
|
344
|
+
});
|
|
345
|
+
await t.step("export() stores inbound activity from span event", async () => {
|
|
346
|
+
const kv = new MemoryKvStore();
|
|
347
|
+
const exporter = new FedifySpanExporter(kv);
|
|
348
|
+
const traceId = "trace123";
|
|
349
|
+
const spanId = "span456";
|
|
350
|
+
const activity = {
|
|
351
|
+
"@context": "https://www.w3.org/ns/activitystreams",
|
|
352
|
+
type: "Create",
|
|
353
|
+
id: "https://example.com/activities/123",
|
|
354
|
+
actor: "https://example.com/users/alice",
|
|
355
|
+
object: {
|
|
356
|
+
type: "Note",
|
|
357
|
+
content: "Hello!"
|
|
358
|
+
}
|
|
359
|
+
};
|
|
360
|
+
const activityJson = JSON.stringify(activity);
|
|
361
|
+
const span = createMockSpan({
|
|
362
|
+
traceId,
|
|
363
|
+
spanId,
|
|
364
|
+
name: "activitypub.inbox",
|
|
365
|
+
events: [createActivityReceivedEvent({
|
|
366
|
+
activityJson,
|
|
367
|
+
verified: true,
|
|
368
|
+
httpSigVerified: true
|
|
369
|
+
})]
|
|
370
|
+
});
|
|
371
|
+
await new Promise((resolve) => {
|
|
372
|
+
exporter.export([span], (result) => {
|
|
373
|
+
assertEquals(result.code, 0);
|
|
374
|
+
resolve();
|
|
375
|
+
});
|
|
376
|
+
});
|
|
377
|
+
const activities = await exporter.getActivitiesByTraceId(traceId);
|
|
378
|
+
assertEquals(activities.length, 1);
|
|
379
|
+
assertEquals(activities[0].traceId, traceId);
|
|
380
|
+
assertEquals(activities[0].spanId, spanId);
|
|
381
|
+
assertEquals(activities[0].direction, "inbound");
|
|
382
|
+
assertEquals(activities[0].activityType, activity.type);
|
|
383
|
+
assertEquals(activities[0].activityId, activity.id);
|
|
384
|
+
assertEquals(activities[0].activityJson, activityJson);
|
|
385
|
+
assertEquals(activities[0].verified, true);
|
|
386
|
+
});
|
|
387
|
+
await t.step("export() stores outbound activity from span event", async () => {
|
|
388
|
+
const kv = new MemoryKvStore();
|
|
389
|
+
const exporter = new FedifySpanExporter(kv);
|
|
390
|
+
const traceId = "trace789";
|
|
391
|
+
const spanId = "span012";
|
|
392
|
+
const inboxUrl = "https://example.com/users/alice/inbox";
|
|
393
|
+
const activity = {
|
|
394
|
+
"@context": "https://www.w3.org/ns/activitystreams",
|
|
395
|
+
type: "Follow",
|
|
396
|
+
id: "https://myserver.com/activities/789",
|
|
397
|
+
actor: "https://myserver.com/users/bob",
|
|
398
|
+
object: "https://example.com/users/alice"
|
|
399
|
+
};
|
|
400
|
+
const activityJson = JSON.stringify(activity);
|
|
401
|
+
const span = createMockSpan({
|
|
402
|
+
traceId,
|
|
403
|
+
spanId,
|
|
404
|
+
name: "activitypub.send_activity",
|
|
405
|
+
events: [createActivitySentEvent({
|
|
406
|
+
activityJson,
|
|
407
|
+
inboxUrl,
|
|
408
|
+
activityId: activity.id
|
|
409
|
+
})]
|
|
410
|
+
});
|
|
411
|
+
await new Promise((resolve) => {
|
|
412
|
+
exporter.export([span], (result) => {
|
|
413
|
+
assertEquals(result.code, 0);
|
|
414
|
+
resolve();
|
|
415
|
+
});
|
|
416
|
+
});
|
|
417
|
+
const activities = await exporter.getActivitiesByTraceId(traceId);
|
|
418
|
+
assertEquals(activities.length, 1);
|
|
419
|
+
assertEquals(activities[0].traceId, traceId);
|
|
420
|
+
assertEquals(activities[0].spanId, spanId);
|
|
421
|
+
assertEquals(activities[0].direction, "outbound");
|
|
422
|
+
assertEquals(activities[0].activityType, activity.type);
|
|
423
|
+
assertEquals(activities[0].activityId, activity.id);
|
|
424
|
+
assertEquals(activities[0].inboxUrl, inboxUrl);
|
|
425
|
+
});
|
|
426
|
+
await t.step("export() ignores spans without activity events", async () => {
|
|
427
|
+
const kv = new MemoryKvStore();
|
|
428
|
+
const exporter = new FedifySpanExporter(kv);
|
|
429
|
+
const span = createMockSpan({
|
|
430
|
+
traceId: "trace999",
|
|
431
|
+
spanId: "span999",
|
|
432
|
+
name: "some-other-span",
|
|
433
|
+
events: []
|
|
434
|
+
});
|
|
435
|
+
await new Promise((resolve) => {
|
|
436
|
+
exporter.export([span], (result) => {
|
|
437
|
+
assertEquals(result.code, 0);
|
|
438
|
+
resolve();
|
|
439
|
+
});
|
|
440
|
+
});
|
|
441
|
+
const activities = await exporter.getActivitiesByTraceId("trace999");
|
|
442
|
+
assertEquals(activities.length, 0);
|
|
443
|
+
});
|
|
444
|
+
await t.step("export() stores multiple activities from same trace", async () => {
|
|
445
|
+
const kv = new MemoryKvStore();
|
|
446
|
+
const exporter = new FedifySpanExporter(kv);
|
|
447
|
+
const inboundActivity = JSON.stringify({
|
|
448
|
+
"@context": "https://www.w3.org/ns/activitystreams",
|
|
449
|
+
type: "Create",
|
|
450
|
+
id: "https://example.com/activities/1"
|
|
451
|
+
});
|
|
452
|
+
const outboundActivity = JSON.stringify({
|
|
453
|
+
"@context": "https://www.w3.org/ns/activitystreams",
|
|
454
|
+
type: "Accept",
|
|
455
|
+
id: "https://myserver.com/activities/2"
|
|
456
|
+
});
|
|
457
|
+
const span1 = createMockSpan({
|
|
458
|
+
traceId: "multitrace",
|
|
459
|
+
spanId: "span1",
|
|
460
|
+
name: "activitypub.inbox",
|
|
461
|
+
events: [createActivityReceivedEvent({ activityJson: inboundActivity })]
|
|
462
|
+
});
|
|
463
|
+
const span2 = createMockSpan({
|
|
464
|
+
traceId: "multitrace",
|
|
465
|
+
spanId: "span2",
|
|
466
|
+
parentSpanId: "span1",
|
|
467
|
+
name: "activitypub.send_activity",
|
|
468
|
+
events: [createActivitySentEvent({
|
|
469
|
+
activityJson: outboundActivity,
|
|
470
|
+
inboxUrl: "https://example.com/inbox"
|
|
471
|
+
})]
|
|
472
|
+
});
|
|
473
|
+
await new Promise((resolve) => {
|
|
474
|
+
exporter.export([span1, span2], (result) => {
|
|
475
|
+
assertEquals(result.code, 0);
|
|
476
|
+
resolve();
|
|
477
|
+
});
|
|
478
|
+
});
|
|
479
|
+
const activities = await exporter.getActivitiesByTraceId("multitrace");
|
|
480
|
+
assertEquals(activities.length, 2);
|
|
481
|
+
const inbound = activities.find((a) => a.direction === "inbound");
|
|
482
|
+
const outbound = activities.find((a) => a.direction === "outbound");
|
|
483
|
+
assertEquals(inbound?.activityType, "Create");
|
|
484
|
+
assertEquals(outbound?.activityType, "Accept");
|
|
485
|
+
assertEquals(outbound?.parentSpanId, "span1");
|
|
486
|
+
});
|
|
487
|
+
await t.step("getRecentTraces() returns recent traces", async () => {
|
|
488
|
+
const kv = new MemoryKvStore();
|
|
489
|
+
const exporter = new FedifySpanExporter(kv);
|
|
490
|
+
for (let i = 0; i < 5; i++) {
|
|
491
|
+
const activityJson = JSON.stringify({
|
|
492
|
+
"@context": "https://www.w3.org/ns/activitystreams",
|
|
493
|
+
type: "Create",
|
|
494
|
+
id: `https://example.com/activities/${i}`
|
|
495
|
+
});
|
|
496
|
+
const span = createMockSpan({
|
|
497
|
+
traceId: `trace-${i}`,
|
|
498
|
+
spanId: `span-${i}`,
|
|
499
|
+
name: "activitypub.inbox",
|
|
500
|
+
events: [createActivityReceivedEvent({ activityJson })]
|
|
501
|
+
});
|
|
502
|
+
await new Promise((resolve) => {
|
|
503
|
+
exporter.export([span], () => resolve());
|
|
504
|
+
});
|
|
505
|
+
}
|
|
506
|
+
const traces = await exporter.getRecentTraces({ limit: 3 });
|
|
507
|
+
assertEquals(traces.length, 3);
|
|
508
|
+
});
|
|
509
|
+
await t.step("getRecentTraces() returns all traces when limit not specified", async () => {
|
|
510
|
+
const kv = new MemoryKvStore();
|
|
511
|
+
const exporter = new FedifySpanExporter(kv);
|
|
512
|
+
for (let i = 0; i < 3; i++) {
|
|
513
|
+
const activityJson = JSON.stringify({
|
|
514
|
+
"@context": "https://www.w3.org/ns/activitystreams",
|
|
515
|
+
type: "Create",
|
|
516
|
+
id: `https://example.com/activities/${i}`
|
|
517
|
+
});
|
|
518
|
+
const span = createMockSpan({
|
|
519
|
+
traceId: `all-trace-${i}`,
|
|
520
|
+
spanId: `span-${i}`,
|
|
521
|
+
name: "activitypub.inbox",
|
|
522
|
+
events: [createActivityReceivedEvent({ activityJson })]
|
|
523
|
+
});
|
|
524
|
+
await new Promise((resolve) => {
|
|
525
|
+
exporter.export([span], () => resolve());
|
|
526
|
+
});
|
|
527
|
+
}
|
|
528
|
+
const traces = await exporter.getRecentTraces();
|
|
529
|
+
assertEquals(traces.length >= 3, true);
|
|
530
|
+
});
|
|
531
|
+
await t.step("forceFlush() returns resolved promise", async () => {
|
|
532
|
+
const kv = new MemoryKvStore();
|
|
533
|
+
const exporter = new FedifySpanExporter(kv);
|
|
534
|
+
await exporter.forceFlush();
|
|
535
|
+
});
|
|
536
|
+
await t.step("shutdown() completes successfully", async () => {
|
|
537
|
+
const kv = new MemoryKvStore();
|
|
538
|
+
const exporter = new FedifySpanExporter(kv);
|
|
539
|
+
await exporter.shutdown();
|
|
540
|
+
});
|
|
541
|
+
await t.step("works with custom KvStore implementation", async () => {
|
|
542
|
+
const storedData = {};
|
|
543
|
+
const kv = {
|
|
544
|
+
get: (key) => {
|
|
545
|
+
const k = JSON.stringify(key);
|
|
546
|
+
return Promise.resolve(storedData[k]);
|
|
547
|
+
},
|
|
548
|
+
set: (key, value) => {
|
|
549
|
+
const k = JSON.stringify(key);
|
|
550
|
+
storedData[k] = value;
|
|
551
|
+
return Promise.resolve();
|
|
552
|
+
},
|
|
553
|
+
delete: (key) => {
|
|
554
|
+
const k = JSON.stringify(key);
|
|
555
|
+
delete storedData[k];
|
|
556
|
+
return Promise.resolve();
|
|
557
|
+
},
|
|
558
|
+
async *list(prefix) {
|
|
559
|
+
for (const [encodedKey, value] of Object.entries(storedData)) {
|
|
560
|
+
const key = JSON.parse(encodedKey);
|
|
561
|
+
if (prefix != null) {
|
|
562
|
+
if (key.length < prefix.length) continue;
|
|
563
|
+
if (!prefix.every((p, i) => key[i] === p)) continue;
|
|
564
|
+
}
|
|
565
|
+
yield {
|
|
566
|
+
key,
|
|
567
|
+
value
|
|
568
|
+
};
|
|
569
|
+
}
|
|
570
|
+
}
|
|
571
|
+
};
|
|
572
|
+
const exporter = new FedifySpanExporter(kv);
|
|
573
|
+
const activityJson = JSON.stringify({
|
|
574
|
+
"@context": "https://www.w3.org/ns/activitystreams",
|
|
575
|
+
type: "Like",
|
|
576
|
+
id: "https://example.com/activities/like"
|
|
577
|
+
});
|
|
578
|
+
const span = createMockSpan({
|
|
579
|
+
traceId: "cas-trace",
|
|
580
|
+
spanId: "cas-span",
|
|
581
|
+
name: "activitypub.inbox",
|
|
582
|
+
events: [createActivityReceivedEvent({ activityJson })]
|
|
583
|
+
});
|
|
584
|
+
await new Promise((resolve) => {
|
|
585
|
+
exporter.export([span], (result) => {
|
|
586
|
+
assertEquals(result.code, 0);
|
|
587
|
+
resolve();
|
|
588
|
+
});
|
|
589
|
+
});
|
|
590
|
+
const activities = await exporter.getActivitiesByTraceId("cas-trace");
|
|
591
|
+
assertEquals(activities.length, 1);
|
|
592
|
+
assertEquals(activities[0].activityType, "Like");
|
|
593
|
+
});
|
|
594
|
+
await t.step("TTL option is respected", async () => {
|
|
595
|
+
const kv = new MemoryKvStore();
|
|
596
|
+
const exporter = new FedifySpanExporter(kv, { ttl: Temporal.Duration.from({ hours: 1 }) });
|
|
597
|
+
const activityJson = JSON.stringify({
|
|
598
|
+
"@context": "https://www.w3.org/ns/activitystreams",
|
|
599
|
+
type: "Create"
|
|
600
|
+
});
|
|
601
|
+
const span = createMockSpan({
|
|
602
|
+
traceId: "ttl-trace",
|
|
603
|
+
spanId: "ttl-span",
|
|
604
|
+
name: "activitypub.inbox",
|
|
605
|
+
events: [createActivityReceivedEvent({ activityJson })]
|
|
606
|
+
});
|
|
607
|
+
await new Promise((resolve) => {
|
|
608
|
+
exporter.export([span], () => resolve());
|
|
609
|
+
});
|
|
610
|
+
const activities = await exporter.getActivitiesByTraceId("ttl-trace");
|
|
611
|
+
assertEquals(activities.length, 1);
|
|
612
|
+
});
|
|
613
|
+
await t.step("keyPrefix option customizes storage keys", async () => {
|
|
614
|
+
const kv = new MemoryKvStore();
|
|
615
|
+
const exporter = new FedifySpanExporter(kv, { keyPrefix: ["custom", "prefix"] });
|
|
616
|
+
const activityJson = JSON.stringify({
|
|
617
|
+
"@context": "https://www.w3.org/ns/activitystreams",
|
|
618
|
+
type: "Announce"
|
|
619
|
+
});
|
|
620
|
+
const span = createMockSpan({
|
|
621
|
+
traceId: "prefix-trace",
|
|
622
|
+
spanId: "prefix-span",
|
|
623
|
+
name: "activitypub.inbox",
|
|
624
|
+
events: [createActivityReceivedEvent({ activityJson })]
|
|
625
|
+
});
|
|
626
|
+
await new Promise((resolve) => {
|
|
627
|
+
exporter.export([span], () => resolve());
|
|
628
|
+
});
|
|
629
|
+
const activities = await exporter.getActivitiesByTraceId("prefix-trace");
|
|
630
|
+
assertEquals(activities.length, 1);
|
|
631
|
+
assertEquals(activities[0].activityType, "Announce");
|
|
632
|
+
});
|
|
633
|
+
await t.step("separate exporter instances share state via same KvStore (distributed simulation)", async () => {
|
|
634
|
+
const sharedKv = new MemoryKvStore();
|
|
635
|
+
const webServerExporter = new FedifySpanExporter(sharedKv);
|
|
636
|
+
const workerExporter = new FedifySpanExporter(sharedKv);
|
|
637
|
+
const dashboardExporter = new FedifySpanExporter(sharedKv);
|
|
638
|
+
const inboundActivity = JSON.stringify({
|
|
639
|
+
"@context": "https://www.w3.org/ns/activitystreams",
|
|
640
|
+
type: "Follow",
|
|
641
|
+
id: "https://remote.example/activities/follow-1",
|
|
642
|
+
actor: "https://remote.example/users/alice",
|
|
643
|
+
object: "https://local.example/users/bob"
|
|
644
|
+
});
|
|
645
|
+
const inboxSpan = createMockSpan({
|
|
646
|
+
traceId: "distributed-trace-001",
|
|
647
|
+
spanId: "inbox-span",
|
|
648
|
+
name: "activitypub.inbox",
|
|
649
|
+
events: [createActivityReceivedEvent({
|
|
650
|
+
activityJson: inboundActivity,
|
|
651
|
+
verified: true
|
|
652
|
+
})]
|
|
653
|
+
});
|
|
654
|
+
await new Promise((resolve) => {
|
|
655
|
+
webServerExporter.export([inboxSpan], () => resolve());
|
|
656
|
+
});
|
|
657
|
+
const outboundActivity = JSON.stringify({
|
|
658
|
+
"@context": "https://www.w3.org/ns/activitystreams",
|
|
659
|
+
type: "Accept",
|
|
660
|
+
id: "https://local.example/activities/accept-1",
|
|
661
|
+
actor: "https://local.example/users/bob",
|
|
662
|
+
object: "https://remote.example/activities/follow-1"
|
|
663
|
+
});
|
|
664
|
+
const sendSpan = createMockSpan({
|
|
665
|
+
traceId: "distributed-trace-001",
|
|
666
|
+
spanId: "send-span",
|
|
667
|
+
parentSpanId: "inbox-span",
|
|
668
|
+
name: "activitypub.send_activity",
|
|
669
|
+
events: [createActivitySentEvent({
|
|
670
|
+
activityJson: outboundActivity,
|
|
671
|
+
inboxUrl: "https://remote.example/users/alice/inbox",
|
|
672
|
+
activityId: "https://local.example/activities/accept-1"
|
|
673
|
+
})]
|
|
674
|
+
});
|
|
675
|
+
await new Promise((resolve) => {
|
|
676
|
+
workerExporter.export([sendSpan], () => resolve());
|
|
677
|
+
});
|
|
678
|
+
const activities = await dashboardExporter.getActivitiesByTraceId("distributed-trace-001");
|
|
679
|
+
assertEquals(activities.length, 2);
|
|
680
|
+
const follow = activities.find((a) => a.activityType === "Follow");
|
|
681
|
+
const accept = activities.find((a) => a.activityType === "Accept");
|
|
682
|
+
assertEquals(follow != null, true);
|
|
683
|
+
assertEquals(follow?.direction, "inbound");
|
|
684
|
+
assertEquals(follow?.verified, true);
|
|
685
|
+
assertEquals(accept != null, true);
|
|
686
|
+
assertEquals(accept?.direction, "outbound");
|
|
687
|
+
assertEquals(accept?.inboxUrl, "https://remote.example/users/alice/inbox");
|
|
688
|
+
assertEquals(accept?.parentSpanId, "inbox-span");
|
|
689
|
+
const recentTraces = await dashboardExporter.getRecentTraces();
|
|
690
|
+
const ourTrace = recentTraces.find((t$1) => t$1.traceId === "distributed-trace-001");
|
|
691
|
+
assertEquals(ourTrace != null, true);
|
|
692
|
+
assertEquals(ourTrace?.activityCount, 2);
|
|
693
|
+
assertEquals(ourTrace?.activityTypes.includes("Follow"), true);
|
|
694
|
+
assertEquals(ourTrace?.activityTypes.includes("Accept"), true);
|
|
695
|
+
});
|
|
696
|
+
await t.step("multiple workers writing to same trace concurrently", async () => {
|
|
697
|
+
const sharedKv = new MemoryKvStore();
|
|
698
|
+
const workers = [
|
|
699
|
+
new FedifySpanExporter(sharedKv),
|
|
700
|
+
new FedifySpanExporter(sharedKv),
|
|
701
|
+
new FedifySpanExporter(sharedKv)
|
|
702
|
+
];
|
|
703
|
+
const traceId = "concurrent-fanout-trace";
|
|
704
|
+
const exportPromises = workers.map((worker, i) => {
|
|
705
|
+
const activityJson = JSON.stringify({
|
|
706
|
+
"@context": "https://www.w3.org/ns/activitystreams",
|
|
707
|
+
type: "Create",
|
|
708
|
+
id: "https://local.example/activities/post-1"
|
|
709
|
+
});
|
|
710
|
+
const span = createMockSpan({
|
|
711
|
+
traceId,
|
|
712
|
+
spanId: `worker-${i}-span`,
|
|
713
|
+
name: "activitypub.send_activity",
|
|
714
|
+
events: [createActivitySentEvent({
|
|
715
|
+
activityJson,
|
|
716
|
+
inboxUrl: `https://follower-${i}.example/inbox`
|
|
717
|
+
})]
|
|
718
|
+
});
|
|
719
|
+
return new Promise((resolve) => {
|
|
720
|
+
worker.export([span], () => resolve());
|
|
721
|
+
});
|
|
722
|
+
});
|
|
723
|
+
await Promise.all(exportPromises);
|
|
724
|
+
const dashboard = new FedifySpanExporter(sharedKv);
|
|
725
|
+
const activities = await dashboard.getActivitiesByTraceId(traceId);
|
|
726
|
+
assertEquals(activities.length, 3);
|
|
727
|
+
const inboxUrls = activities.map((a) => a.inboxUrl).sort();
|
|
728
|
+
assertEquals(inboxUrls, [
|
|
729
|
+
"https://follower-0.example/inbox",
|
|
730
|
+
"https://follower-1.example/inbox",
|
|
731
|
+
"https://follower-2.example/inbox"
|
|
732
|
+
]);
|
|
733
|
+
});
|
|
734
|
+
await t.step("extracts actorId from activity with string actor", async () => {
|
|
735
|
+
const kv = new MemoryKvStore();
|
|
736
|
+
const exporter = new FedifySpanExporter(kv);
|
|
737
|
+
const activityJson = JSON.stringify({
|
|
738
|
+
"@context": "https://www.w3.org/ns/activitystreams",
|
|
739
|
+
type: "Create",
|
|
740
|
+
id: "https://example.com/activities/123",
|
|
741
|
+
actor: "https://example.com/users/alice",
|
|
742
|
+
object: {
|
|
743
|
+
type: "Note",
|
|
744
|
+
content: "Hello!"
|
|
745
|
+
}
|
|
746
|
+
});
|
|
747
|
+
const span = createMockSpan({
|
|
748
|
+
traceId: "actor-string-trace",
|
|
749
|
+
spanId: "span1",
|
|
750
|
+
name: "activitypub.inbox",
|
|
751
|
+
events: [createActivityReceivedEvent({ activityJson })]
|
|
752
|
+
});
|
|
753
|
+
await new Promise((resolve) => {
|
|
754
|
+
exporter.export([span], () => resolve());
|
|
755
|
+
});
|
|
756
|
+
const activities = await exporter.getActivitiesByTraceId("actor-string-trace");
|
|
757
|
+
assertEquals(activities.length, 1);
|
|
758
|
+
assertEquals(activities[0].actorId, "https://example.com/users/alice");
|
|
759
|
+
});
|
|
760
|
+
await t.step("extracts actorId from activity with object actor", async () => {
|
|
761
|
+
const kv = new MemoryKvStore();
|
|
762
|
+
const exporter = new FedifySpanExporter(kv);
|
|
763
|
+
const activityJson = JSON.stringify({
|
|
764
|
+
"@context": "https://www.w3.org/ns/activitystreams",
|
|
765
|
+
type: "Create",
|
|
766
|
+
id: "https://example.com/activities/456",
|
|
767
|
+
actor: {
|
|
768
|
+
type: "Person",
|
|
769
|
+
id: "https://example.com/users/bob",
|
|
770
|
+
name: "Bob"
|
|
771
|
+
},
|
|
772
|
+
object: {
|
|
773
|
+
type: "Note",
|
|
774
|
+
content: "Hello!"
|
|
775
|
+
}
|
|
776
|
+
});
|
|
777
|
+
const span = createMockSpan({
|
|
778
|
+
traceId: "actor-object-trace",
|
|
779
|
+
spanId: "span1",
|
|
780
|
+
name: "activitypub.inbox",
|
|
781
|
+
events: [createActivityReceivedEvent({ activityJson })]
|
|
782
|
+
});
|
|
783
|
+
await new Promise((resolve) => {
|
|
784
|
+
exporter.export([span], () => resolve());
|
|
785
|
+
});
|
|
786
|
+
const activities = await exporter.getActivitiesByTraceId("actor-object-trace");
|
|
787
|
+
assertEquals(activities.length, 1);
|
|
788
|
+
assertEquals(activities[0].actorId, "https://example.com/users/bob");
|
|
789
|
+
});
|
|
790
|
+
await t.step("extracts actorId from outbound activity", async () => {
|
|
791
|
+
const kv = new MemoryKvStore();
|
|
792
|
+
const exporter = new FedifySpanExporter(kv);
|
|
793
|
+
const activityJson = JSON.stringify({
|
|
794
|
+
"@context": "https://www.w3.org/ns/activitystreams",
|
|
795
|
+
type: "Follow",
|
|
796
|
+
id: "https://myserver.com/activities/789",
|
|
797
|
+
actor: "https://myserver.com/users/charlie",
|
|
798
|
+
object: "https://example.com/users/alice"
|
|
799
|
+
});
|
|
800
|
+
const span = createMockSpan({
|
|
801
|
+
traceId: "outbound-actor-trace",
|
|
802
|
+
spanId: "span1",
|
|
803
|
+
name: "activitypub.send_activity",
|
|
804
|
+
events: [createActivitySentEvent({
|
|
805
|
+
activityJson,
|
|
806
|
+
inboxUrl: "https://example.com/users/alice/inbox"
|
|
807
|
+
})]
|
|
808
|
+
});
|
|
809
|
+
await new Promise((resolve) => {
|
|
810
|
+
exporter.export([span], () => resolve());
|
|
811
|
+
});
|
|
812
|
+
const activities = await exporter.getActivitiesByTraceId("outbound-actor-trace");
|
|
813
|
+
assertEquals(activities.length, 1);
|
|
814
|
+
assertEquals(activities[0].actorId, "https://myserver.com/users/charlie");
|
|
815
|
+
});
|
|
816
|
+
await t.step("extracts signature verification details for inbound activity", async () => {
|
|
817
|
+
const kv = new MemoryKvStore();
|
|
818
|
+
const exporter = new FedifySpanExporter(kv);
|
|
819
|
+
const activityJson = JSON.stringify({
|
|
820
|
+
"@context": "https://www.w3.org/ns/activitystreams",
|
|
821
|
+
type: "Create",
|
|
822
|
+
id: "https://example.com/activities/sig-test",
|
|
823
|
+
actor: "https://example.com/users/alice"
|
|
824
|
+
});
|
|
825
|
+
const span = createMockSpan({
|
|
826
|
+
traceId: "sig-details-trace",
|
|
827
|
+
spanId: "span1",
|
|
828
|
+
name: "activitypub.inbox",
|
|
829
|
+
events: [createActivityReceivedEvent({
|
|
830
|
+
activityJson,
|
|
831
|
+
verified: true,
|
|
832
|
+
httpSigVerified: true,
|
|
833
|
+
httpSigKeyId: "https://example.com/users/alice#main-key",
|
|
834
|
+
ldSigVerified: false
|
|
835
|
+
})]
|
|
836
|
+
});
|
|
837
|
+
await new Promise((resolve) => {
|
|
838
|
+
exporter.export([span], () => resolve());
|
|
839
|
+
});
|
|
840
|
+
const activities = await exporter.getActivitiesByTraceId("sig-details-trace");
|
|
841
|
+
assertEquals(activities.length, 1);
|
|
842
|
+
assertEquals(activities[0].verified, true);
|
|
843
|
+
assertEquals(activities[0].signatureDetails != null, true);
|
|
844
|
+
assertEquals(activities[0].signatureDetails?.httpSignaturesVerified, true);
|
|
845
|
+
assertEquals(activities[0].signatureDetails?.httpSignaturesKeyId, "https://example.com/users/alice#main-key");
|
|
846
|
+
assertEquals(activities[0].signatureDetails?.ldSignaturesVerified, false);
|
|
847
|
+
});
|
|
848
|
+
await t.step("signature details with LD signatures verified", async () => {
|
|
849
|
+
const kv = new MemoryKvStore();
|
|
850
|
+
const exporter = new FedifySpanExporter(kv);
|
|
851
|
+
const activityJson = JSON.stringify({
|
|
852
|
+
"@context": "https://www.w3.org/ns/activitystreams",
|
|
853
|
+
type: "Delete",
|
|
854
|
+
id: "https://example.com/activities/ld-sig-test",
|
|
855
|
+
actor: "https://example.com/users/alice"
|
|
856
|
+
});
|
|
857
|
+
const span = createMockSpan({
|
|
858
|
+
traceId: "ld-sig-trace",
|
|
859
|
+
spanId: "span1",
|
|
860
|
+
name: "activitypub.inbox",
|
|
861
|
+
events: [createActivityReceivedEvent({
|
|
862
|
+
activityJson,
|
|
863
|
+
verified: true,
|
|
864
|
+
httpSigVerified: false,
|
|
865
|
+
ldSigVerified: true
|
|
866
|
+
})]
|
|
867
|
+
});
|
|
868
|
+
await new Promise((resolve) => {
|
|
869
|
+
exporter.export([span], () => resolve());
|
|
870
|
+
});
|
|
871
|
+
const activities = await exporter.getActivitiesByTraceId("ld-sig-trace");
|
|
872
|
+
assertEquals(activities.length, 1);
|
|
873
|
+
assertEquals(activities[0].signatureDetails?.httpSignaturesVerified, false);
|
|
874
|
+
assertEquals(activities[0].signatureDetails?.ldSignaturesVerified, true);
|
|
875
|
+
});
|
|
876
|
+
await t.step("handles activity without actor field", async () => {
|
|
877
|
+
const kv = new MemoryKvStore();
|
|
878
|
+
const exporter = new FedifySpanExporter(kv);
|
|
879
|
+
const activityJson = JSON.stringify({
|
|
880
|
+
"@context": "https://www.w3.org/ns/activitystreams",
|
|
881
|
+
type: "Delete",
|
|
882
|
+
id: "https://example.com/activities/no-actor",
|
|
883
|
+
object: "https://example.com/posts/123"
|
|
884
|
+
});
|
|
885
|
+
const span = createMockSpan({
|
|
886
|
+
traceId: "no-actor-trace",
|
|
887
|
+
spanId: "span1",
|
|
888
|
+
name: "activitypub.inbox",
|
|
889
|
+
events: [createActivityReceivedEvent({ activityJson })]
|
|
890
|
+
});
|
|
891
|
+
await new Promise((resolve) => {
|
|
892
|
+
exporter.export([span], () => resolve());
|
|
893
|
+
});
|
|
894
|
+
const activities = await exporter.getActivitiesByTraceId("no-actor-trace");
|
|
895
|
+
assertEquals(activities.length, 1);
|
|
896
|
+
assertEquals(activities[0].actorId, void 0);
|
|
897
|
+
});
|
|
898
|
+
});
|
|
899
|
+
|
|
900
|
+
//#endregion
|