@fedify/fedify 2.3.0-dev.1137 → 2.3.0-dev.1150
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-BCkBXxky.mjs → builder-Bjm1Jq9n.mjs} +2 -2
- 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/{context-DI2gRbyN.d.cts → context-CRXCkTM6.d.cts} +48 -6
- package/dist/{context-DCtsSHDv.d.ts → context-MgCh7YGu.d.ts} +48 -6
- package/dist/{deno-B_9yJW3w.mjs → deno-CKFE6Uya.mjs} +1 -1
- package/dist/{docloader-BT89tyFr.mjs → docloader-B-ZE1cZf.mjs} +2 -2
- package/dist/federation/builder.test.mjs +1 -1
- package/dist/federation/handler.test.mjs +1363 -44
- package/dist/federation/idempotency.test.mjs +2 -2
- package/dist/federation/metrics.test.mjs +60 -1
- package/dist/federation/middleware.test.mjs +1667 -163
- 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/retry.test.mjs +1 -1
- package/dist/federation/send.test.mjs +8 -8
- package/dist/federation/temporal.test.d.mts +2 -0
- package/dist/federation/temporal.test.mjs +71 -0
- package/dist/federation/webfinger.test.mjs +147 -2
- 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-CWoeyogl.cjs → http-DQYEA7AZ.cjs} +53 -1
- package/dist/{http-CToqG5ap.js → http-WbS1gKzr.js} +48 -2
- package/dist/{http-Cyx5SNuu.mjs → http-vHCgbhTg.mjs} +3 -3
- package/dist/{key-CkkMJBjF.mjs → key-N0zP_oJA.mjs} +2 -2
- package/dist/{kv-cache-CuCn2xvM.js → kv-cache-DM2O-Yjy.js} +1 -1
- package/dist/{kv-cache-DuEwFYcN.cjs → kv-cache-Dsg_bi4N.cjs} +1 -1
- package/dist/{kv-cache-VHFP42vY.mjs → kv-cache-GXXZEemD.mjs} +1 -1
- package/dist/{ld-k8yqD2a-.mjs → ld-BwKhquPx.mjs} +302 -6
- package/dist/{metrics-iRBg8jTk.mjs → metrics-7Vy9FvEw.mjs} +48 -2
- package/dist/{middleware-D7FrhN9q.js → middleware-BscgvU-m.js} +496 -115
- package/dist/{middleware-BWLUrbS9.cjs → middleware-D_iXrYHJ.cjs} +497 -115
- package/dist/{middleware-CztxpARM.mjs → middleware-Db1_qAFG.mjs} +1 -1
- package/dist/{middleware-DQEgdr83.mjs → middleware-ZuUcO0t1.mjs} +416 -124
- package/dist/{mod-C504qevA.d.cts → mod-C7HOzGqH.d.cts} +11 -2
- package/dist/{mod-wYfuXeDE.d.ts → mod-CpQHB3Ys.d.ts} +11 -2
- package/dist/mod.cjs +4 -4
- package/dist/mod.d.cts +2 -2
- package/dist/mod.d.ts +2 -2
- package/dist/mod.js +4 -4
- package/dist/nodeinfo/handler.test.mjs +1 -1
- package/dist/{owner-nmXdvXpc.mjs → owner-FD0H_vpj.mjs} +2 -2
- package/dist/{proof-CcsIJLTn.cjs → proof-CYK8T8IS.cjs} +353 -3
- package/dist/{proof-NRmtrTDu.js → proof-I3EokKN-.js} +300 -4
- package/dist/{proof-DpwO1T4S.mjs → proof-V_lafPmA.mjs} +3 -3
- package/dist/{send-DvX2tYyZ.mjs → send-Cc2_10tF.mjs} +3 -3
- package/dist/sig/http.test.mjs +2 -2
- package/dist/sig/key.test.mjs +1 -1
- package/dist/sig/ld.test.mjs +558 -2
- package/dist/sig/mod.cjs +2 -2
- package/dist/sig/mod.js +2 -2
- package/dist/sig/owner.test.mjs +1 -1
- package/dist/sig/proof.test.mjs +1 -1
- package/dist/temporal-BkmBfs__.mjs +95 -0
- package/dist/testing/mod.d.mts +48 -6
- 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.js +1 -1
- package/package.json +6 -6
- /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/{retry-v_sGLH1d.mjs → retry-_VvV0h9f.mjs} +0 -0
|
@@ -1,25 +1,26 @@
|
|
|
1
1
|
import { Temporal } from "@js-temporal/polyfill";
|
|
2
2
|
import "urlpattern-polyfill";
|
|
3
3
|
globalThis.addEventListener = () => {};
|
|
4
|
-
import { n as version, t as name } from "./deno-
|
|
5
|
-
import { a as instrumentDocumentLoader, d as recordInboxActivity, i as getRemoteHost, m as recordOutboxEnqueue, n as getDurationMs, o as isAbortError, p as recordOutboxActivity, r as getFederationMetrics, u as recordFanoutRecipients } from "./metrics-
|
|
4
|
+
import { n as version, t as name } from "./deno-CKFE6Uya.mjs";
|
|
5
|
+
import { a as instrumentDocumentLoader, d as recordInboxActivity, h as recordWebFingerHandle, i as getRemoteHost, m as recordOutboxEnqueue, n as getDurationMs, o as isAbortError, p as recordOutboxActivity, r as getFederationMetrics, u as recordFanoutRecipients } from "./metrics-7Vy9FvEw.mjs";
|
|
6
6
|
import { t as formatAcceptSignature } from "./accept-CceiKpCy.mjs";
|
|
7
|
-
import { a as importJwk, o as validateCryptoKey, t as exportJwk } from "./key-
|
|
8
|
-
import { l as verifyRequest, o as parseRfc9421SignatureInput, u as verifyRequestDetailed } from "./http-
|
|
9
|
-
import { t as getAuthenticatedDocumentLoader } from "./docloader-
|
|
10
|
-
import { n as kvCache } from "./kv-cache-
|
|
11
|
-
import { a as
|
|
12
|
-
import { n as getKeyOwner, t as doesActorOwnKey } from "./owner-
|
|
7
|
+
import { a as importJwk, o as validateCryptoKey, t as exportJwk } from "./key-N0zP_oJA.mjs";
|
|
8
|
+
import { l as verifyRequest, o as parseRfc9421SignatureInput, u as verifyRequestDetailed } from "./http-vHCgbhTg.mjs";
|
|
9
|
+
import { t as getAuthenticatedDocumentLoader } from "./docloader-B-ZE1cZf.mjs";
|
|
10
|
+
import { n as kvCache } from "./kv-cache-GXXZEemD.mjs";
|
|
11
|
+
import { _ as wrapContextLoaderForJsonLd, a as compactJsonLd, c as getNormalizationContextLoader, d as isClearlyMalformedContextReference, f as isInvalidUrlTypeError, l as hasSignature, m as verifyCompactJsonLd, p as signJsonLd, r as assertSafeJsonLd, s as detachSignature, t as InvalidContextReferenceError, u as hasSignatureLike } from "./ld-BwKhquPx.mjs";
|
|
12
|
+
import { n as getKeyOwner, t as doesActorOwnKey } from "./owner-FD0H_vpj.mjs";
|
|
13
13
|
import { r as normalizeOutgoingActivityJsonLd } from "./outgoing-jsonld-BgFLCJQ_.mjs";
|
|
14
|
-
import { i as verifyObject, n as hasProofLike, r as signObject } from "./proof-
|
|
14
|
+
import { i as verifyObject, n as hasProofLike, r as signObject } from "./proof-V_lafPmA.mjs";
|
|
15
15
|
import { t as getNodeInfo } from "./client-B_A6mfn3.mjs";
|
|
16
16
|
import { t as nodeInfoToJson } from "./types-BFowWFTT.mjs";
|
|
17
|
-
import { n as FederationBuilderImpl, t as ACTOR_ALIAS_PREFIX } from "./builder-
|
|
17
|
+
import { n as FederationBuilderImpl, t as ACTOR_ALIAS_PREFIX } from "./builder-Bjm1Jq9n.mjs";
|
|
18
18
|
import { t as buildCollectionSynchronizationHeader } from "./collection-CA3V5zyK.mjs";
|
|
19
19
|
import { t as KvKeyCache } from "./keycache-BYMd8q7F.mjs";
|
|
20
20
|
import { t as acceptsJsonLd } from "./negotiation-CDW-_gUU.mjs";
|
|
21
|
-
import { t as
|
|
22
|
-
import {
|
|
21
|
+
import { t as hasMalformedKnownTemporalLiteral } from "./temporal-BkmBfs__.mjs";
|
|
22
|
+
import { t as createExponentialBackoffPolicy } from "./retry-_VvV0h9f.mjs";
|
|
23
|
+
import { n as extractInboxes, r as sendActivity, t as SendActivityError } from "./send-Cc2_10tF.mjs";
|
|
23
24
|
import { getLogger, withContext } from "@logtape/logtape";
|
|
24
25
|
import { RouterError } from "@fedify/uri-template";
|
|
25
26
|
import { Activity, Collection, CollectionPage, CryptographicKey, Link, Multikey, Object as Object$1, OrderedCollection, OrderedCollectionPage, Tombstone, getTypeId, lookupObject, traverseCollection } from "@fedify/vocab";
|
|
@@ -155,7 +156,7 @@ function handleNodeInfoJrd(_request, context) {
|
|
|
155
156
|
}
|
|
156
157
|
//#endregion
|
|
157
158
|
//#region src/federation/inbox.ts
|
|
158
|
-
async function routeActivity({ context: ctx, json, activity, recipient, inboxListeners, inboxContextFactory, inboxErrorHandler, kv, kvPrefixes, queue, span, meterProvider, tracerProvider, idempotencyStrategy }) {
|
|
159
|
+
async function routeActivity({ context: ctx, json, originalJson, normalizedActivity, ldSignatureVerified, activity, recipient, inboxListeners, inboxContextFactory, listenerInboxContextFactory, inboxErrorHandler, kv, kvPrefixes, queue, span, meterProvider, tracerProvider, idempotencyStrategy }) {
|
|
159
160
|
const logger = getLogger([
|
|
160
161
|
"fedify",
|
|
161
162
|
"federation",
|
|
@@ -214,7 +215,9 @@ async function routeActivity({ context: ctx, json, activity, recipient, inboxLis
|
|
|
214
215
|
type: "inbox",
|
|
215
216
|
id: crypto.randomUUID(),
|
|
216
217
|
baseUrl: ctx.origin,
|
|
217
|
-
activity: json,
|
|
218
|
+
activity: originalJson ?? json,
|
|
219
|
+
...normalizedActivity == null ? {} : { normalizedActivity },
|
|
220
|
+
...ldSignatureVerified == null ? {} : { ldSignatureVerified },
|
|
218
221
|
identifier: recipient,
|
|
219
222
|
attempt: 0,
|
|
220
223
|
started: (/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -268,7 +271,8 @@ async function routeActivity({ context: ctx, json, activity, recipient, inboxLis
|
|
|
268
271
|
const activityType = getTypeId(activity).href;
|
|
269
272
|
const started = performance.now();
|
|
270
273
|
try {
|
|
271
|
-
|
|
274
|
+
const contextFactory = listenerInboxContextFactory ?? inboxContextFactory;
|
|
275
|
+
await listener(contextFactory(recipient, contextFactory === inboxContextFactory ? json : originalJson ?? json, activity.id?.href, activityType), activity);
|
|
272
276
|
} finally {
|
|
273
277
|
getFederationMetrics(meterProvider).recordInboxProcessingDuration(activityType, getDurationMs(started));
|
|
274
278
|
}
|
|
@@ -310,6 +314,33 @@ async function routeActivity({ context: ctx, json, activity, recipient, inboxLis
|
|
|
310
314
|
}
|
|
311
315
|
//#endregion
|
|
312
316
|
//#region src/federation/handler.ts
|
|
317
|
+
const rawInboxContextFactorySymbol = Symbol("fedify.rawInboxContextFactory");
|
|
318
|
+
function isRemoteContextLoadingFailure$1(error) {
|
|
319
|
+
return error instanceof Error && typeof error.details === "object" && error.details != null && error.details.code === "loading remote context failed";
|
|
320
|
+
}
|
|
321
|
+
function isPermanentRemoteContextError$1(error) {
|
|
322
|
+
if (!(error instanceof Error) || error.name !== "jsonld.InvalidUrl") return false;
|
|
323
|
+
const details = error.details;
|
|
324
|
+
if (details?.code === "invalid remote context") return true;
|
|
325
|
+
return isRemoteContextLoadingFailure$1(error) && typeof details?.url === "string" && !URL.canParse(details.url) && isClearlyMalformedContextReference(details.url);
|
|
326
|
+
}
|
|
327
|
+
function isInvalidJsonLdError(error) {
|
|
328
|
+
if (!(error instanceof Error)) return false;
|
|
329
|
+
const name = error.name;
|
|
330
|
+
return name === "UnsafeJsonLdError" || error instanceof InvalidContextReferenceError || isPermanentRemoteContextError$1(error) || name === "jsonld.SyntaxError" && !isRemoteContextLoadingFailure$1(error);
|
|
331
|
+
}
|
|
332
|
+
function isValidationTypeError(error) {
|
|
333
|
+
return error instanceof TypeError && (/^(Invalid JSON-LD:|Invalid type:|Unexpected type:)/.test(error.message) || isInvalidUrlTypeError(error));
|
|
334
|
+
}
|
|
335
|
+
function isPermanentActivityParseError(error) {
|
|
336
|
+
return isInvalidJsonLdError(error) || isValidationTypeError(error);
|
|
337
|
+
}
|
|
338
|
+
function hasHttpSignatureHeaders(request) {
|
|
339
|
+
return request.headers.has("Signature") || request.headers.has("Signature-Input");
|
|
340
|
+
}
|
|
341
|
+
function hasObjectIntegrityProof(json) {
|
|
342
|
+
return typeof json === "object" && json != null && "proof" in json;
|
|
343
|
+
}
|
|
313
344
|
/**
|
|
314
345
|
* Handles an actor request.
|
|
315
346
|
* @template TContextData The context data to pass to the context.
|
|
@@ -381,8 +412,8 @@ async function handleObject(request, { values, context, objectDispatcher, author
|
|
|
381
412
|
* @param parameters The parameters for handling the collection.
|
|
382
413
|
* @returns A promise that resolves to an HTTP response.
|
|
383
414
|
*/
|
|
384
|
-
async function handleCollection(request, { name: name$
|
|
385
|
-
const spanName = name$
|
|
415
|
+
async function handleCollection(request, { name: name$1, identifier, uriGetter, filter, filterPredicate, context, collectionCallbacks, tracerProvider, onUnauthorized, onNotFound }) {
|
|
416
|
+
const spanName = name$1.trim().replace(/\s+/g, "_");
|
|
386
417
|
tracerProvider = tracerProvider ?? trace.getTracerProvider();
|
|
387
418
|
const tracer = tracerProvider.getTracer(name, version);
|
|
388
419
|
const cursor = new URL(request.url).searchParams.get("cursor");
|
|
@@ -424,7 +455,7 @@ async function handleCollection(request, { name: name$2, identifier, uriGetter,
|
|
|
424
455
|
collection = new OrderedCollection({
|
|
425
456
|
id: baseUri,
|
|
426
457
|
totalItems: totalItems == null ? null : Number(totalItems),
|
|
427
|
-
items: filterCollectionItems(itemsOrResponse, name$
|
|
458
|
+
items: filterCollectionItems(itemsOrResponse, name$1, filterPredicate)
|
|
428
459
|
});
|
|
429
460
|
} else {
|
|
430
461
|
const lastCursor = await collectionCallbacks.lastCursor?.(context, identifier);
|
|
@@ -445,7 +476,7 @@ async function handleCollection(request, { name: name$2, identifier, uriGetter,
|
|
|
445
476
|
} else {
|
|
446
477
|
const uri = new URL(baseUri);
|
|
447
478
|
uri.searchParams.set("cursor", cursor);
|
|
448
|
-
const pageOrResponse = await tracer.startActiveSpan(`activitypub.dispatch_collection_page ${name$
|
|
479
|
+
const pageOrResponse = await tracer.startActiveSpan(`activitypub.dispatch_collection_page ${name$1}`, {
|
|
449
480
|
kind: SpanKind.SERVER,
|
|
450
481
|
attributes: {
|
|
451
482
|
"activitypub.collection.id": uri.href,
|
|
@@ -489,7 +520,7 @@ async function handleCollection(request, { name: name$2, identifier, uriGetter,
|
|
|
489
520
|
id: uri,
|
|
490
521
|
prev,
|
|
491
522
|
next,
|
|
492
|
-
items: filterCollectionItems(items, name$
|
|
523
|
+
items: filterCollectionItems(items, name$1, filterPredicate),
|
|
493
524
|
partOf
|
|
494
525
|
});
|
|
495
526
|
}
|
|
@@ -836,29 +867,105 @@ async function handleInboxInternal(request, parameters, span) {
|
|
|
836
867
|
});
|
|
837
868
|
}
|
|
838
869
|
const keyCache = new KvKeyCache(kv, kvPrefixes.publicKey, ctx);
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
870
|
+
const jsonWithoutSig = detachSignature(json);
|
|
871
|
+
const hasLdSignature = hasSignature(json);
|
|
872
|
+
const canAttemptAlternateAuthAfterLdSignatureFailure = skipSignatureVerification || hasHttpSignatureHeaders(request) || hasObjectIntegrityProof(jsonWithoutSig);
|
|
873
|
+
let deferredLdSignatureError = void 0;
|
|
874
|
+
const respondInvalidActivity = async (error) => {
|
|
875
|
+
logger.error("Failed to parse activity:\n{error}", {
|
|
876
|
+
recipient,
|
|
877
|
+
activity: json,
|
|
878
|
+
error
|
|
847
879
|
});
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
880
|
+
try {
|
|
881
|
+
await inboxErrorHandler?.(ctx, error);
|
|
882
|
+
} catch (error) {
|
|
883
|
+
logger.error("An unexpected error occurred in inbox error handler:\n{error}", {
|
|
884
|
+
error,
|
|
885
|
+
activity: json,
|
|
886
|
+
recipient
|
|
887
|
+
});
|
|
888
|
+
}
|
|
889
|
+
span.setStatus({
|
|
890
|
+
code: SpanStatusCode.ERROR,
|
|
891
|
+
message: `Failed to parse activity:\n${error}`
|
|
892
|
+
});
|
|
893
|
+
return new Response("Invalid activity.", {
|
|
894
|
+
status: 400,
|
|
895
|
+
headers: { "Content-Type": "text/plain; charset=utf-8" }
|
|
896
|
+
});
|
|
897
|
+
};
|
|
898
|
+
let compactedJson = json;
|
|
899
|
+
let compactedJsonWithoutSig = jsonWithoutSig;
|
|
900
|
+
let ldSigVerified = false;
|
|
901
|
+
if (hasLdSignature) {
|
|
902
|
+
try {
|
|
903
|
+
compactedJson = await compactJsonLd(json, ctx.contextLoader);
|
|
904
|
+
} catch (error) {
|
|
905
|
+
if (isInvalidJsonLdError(error)) {
|
|
906
|
+
logger.error("Failed to parse JSON-LD:\n{error}", {
|
|
907
|
+
recipient,
|
|
908
|
+
error
|
|
909
|
+
});
|
|
910
|
+
return new Response("Invalid JSON-LD.", {
|
|
911
|
+
status: 400,
|
|
912
|
+
headers: { "Content-Type": "text/plain; charset=utf-8" }
|
|
913
|
+
});
|
|
914
|
+
}
|
|
915
|
+
if (!canAttemptAlternateAuthAfterLdSignatureFailure) throw error;
|
|
916
|
+
if (!skipSignatureVerification) deferredLdSignatureError = error;
|
|
917
|
+
logger.debug("Failed to normalize JSON-LD for Linked Data Signatures; deferring to another authentication path only if it verifies:\n{error}", {
|
|
851
918
|
recipient,
|
|
852
919
|
error
|
|
853
920
|
});
|
|
854
|
-
return new Response("Invalid JSON-LD.", {
|
|
855
|
-
status: 400,
|
|
856
|
-
headers: { "Content-Type": "text/plain; charset=utf-8" }
|
|
857
|
-
});
|
|
858
921
|
}
|
|
859
|
-
|
|
922
|
+
if (compactedJson !== json) {
|
|
923
|
+
compactedJsonWithoutSig = detachSignature(compactedJson);
|
|
924
|
+
try {
|
|
925
|
+
ldSigVerified = await verifyCompactJsonLd(compactedJson, {
|
|
926
|
+
contextLoader: ctx.contextLoader,
|
|
927
|
+
documentLoader: ctx.documentLoader,
|
|
928
|
+
keyCache,
|
|
929
|
+
meterProvider,
|
|
930
|
+
tracerProvider
|
|
931
|
+
});
|
|
932
|
+
} catch (error) {
|
|
933
|
+
if (error instanceof RangeError && await hasMalformedKnownTemporalLiteral(compactedJsonWithoutSig, ctx.contextLoader)) return await respondInvalidActivity(error);
|
|
934
|
+
if (isInvalidJsonLdError(error)) {
|
|
935
|
+
logger.error("Failed to parse JSON-LD:\n{error}", {
|
|
936
|
+
recipient,
|
|
937
|
+
error
|
|
938
|
+
});
|
|
939
|
+
return new Response("Invalid JSON-LD.", {
|
|
940
|
+
status: 400,
|
|
941
|
+
headers: { "Content-Type": "text/plain; charset=utf-8" }
|
|
942
|
+
});
|
|
943
|
+
}
|
|
944
|
+
if (!canAttemptAlternateAuthAfterLdSignatureFailure) throw error;
|
|
945
|
+
if (!skipSignatureVerification) try {
|
|
946
|
+
await Object$1.fromJsonLd(compactedJson, {
|
|
947
|
+
contextLoader: getNormalizationContextLoader(ctx.contextLoader),
|
|
948
|
+
documentLoader: ctx.documentLoader,
|
|
949
|
+
tracerProvider
|
|
950
|
+
});
|
|
951
|
+
} catch (parseError) {
|
|
952
|
+
if (parseError instanceof RangeError && await hasMalformedKnownTemporalLiteral(compactedJsonWithoutSig, ctx.contextLoader)) return await respondInvalidActivity(parseError);
|
|
953
|
+
if (isInvalidJsonLdError(parseError)) {
|
|
954
|
+
logger.error("Failed to parse JSON-LD:\n{error}", {
|
|
955
|
+
recipient,
|
|
956
|
+
error: parseError
|
|
957
|
+
});
|
|
958
|
+
return new Response("Invalid JSON-LD.", {
|
|
959
|
+
status: 400,
|
|
960
|
+
headers: { "Content-Type": "text/plain; charset=utf-8" }
|
|
961
|
+
});
|
|
962
|
+
}
|
|
963
|
+
deferredLdSignatureError = parseError;
|
|
964
|
+
}
|
|
965
|
+
ldSigVerified = false;
|
|
966
|
+
}
|
|
967
|
+
}
|
|
860
968
|
}
|
|
861
|
-
const jsonWithoutSig = detachSignature(json);
|
|
862
969
|
let activity = null;
|
|
863
970
|
let activityVerified = false;
|
|
864
971
|
if (ldSigVerified) {
|
|
@@ -866,7 +973,16 @@ async function handleInboxInternal(request, parameters, span) {
|
|
|
866
973
|
recipient,
|
|
867
974
|
json
|
|
868
975
|
});
|
|
869
|
-
|
|
976
|
+
try {
|
|
977
|
+
activity = await Activity.fromJsonLd(compactedJsonWithoutSig, {
|
|
978
|
+
...ctx,
|
|
979
|
+
contextLoader: getNormalizationContextLoader(ctx.contextLoader)
|
|
980
|
+
});
|
|
981
|
+
} catch (error) {
|
|
982
|
+
if (error instanceof RangeError && await hasMalformedKnownTemporalLiteral(compactedJsonWithoutSig, ctx.contextLoader)) return await respondInvalidActivity(error);
|
|
983
|
+
if (!isPermanentActivityParseError(error)) throw error;
|
|
984
|
+
return await respondInvalidActivity(error);
|
|
985
|
+
}
|
|
870
986
|
activityVerified = true;
|
|
871
987
|
} else {
|
|
872
988
|
logger.debug("Linked Data Signatures are not verified.", {
|
|
@@ -875,13 +991,22 @@ async function handleInboxInternal(request, parameters, span) {
|
|
|
875
991
|
});
|
|
876
992
|
try {
|
|
877
993
|
activity = await verifyObject(Activity, jsonWithoutSig, {
|
|
878
|
-
contextLoader: ctx.contextLoader,
|
|
994
|
+
contextLoader: wrapContextLoaderForJsonLd(ctx.contextLoader),
|
|
879
995
|
documentLoader: ctx.documentLoader,
|
|
880
996
|
keyCache,
|
|
881
997
|
meterProvider,
|
|
882
998
|
tracerProvider
|
|
883
999
|
});
|
|
884
1000
|
} catch (error) {
|
|
1001
|
+
if (error instanceof RangeError && await hasMalformedKnownTemporalLiteral(jsonWithoutSig, ctx.contextLoader)) return await respondInvalidActivity(error);
|
|
1002
|
+
if (deferredLdSignatureError != null) {
|
|
1003
|
+
logger.debug("Object Integrity Proof fallback did not supersede a deferred Linked Data Signature failure:\n{error}", {
|
|
1004
|
+
recipient,
|
|
1005
|
+
error
|
|
1006
|
+
});
|
|
1007
|
+
activity = null;
|
|
1008
|
+
}
|
|
1009
|
+
if (!isPermanentActivityParseError(error)) throw error;
|
|
885
1010
|
logger.error("Failed to parse activity:\n{error}", {
|
|
886
1011
|
recipient,
|
|
887
1012
|
activity: json,
|
|
@@ -930,6 +1055,7 @@ async function handleInboxInternal(request, parameters, span) {
|
|
|
930
1055
|
tracerProvider
|
|
931
1056
|
});
|
|
932
1057
|
if (verification.verified === false) {
|
|
1058
|
+
if (deferredLdSignatureError != null) throw deferredLdSignatureError;
|
|
933
1059
|
const reason = verification.reason;
|
|
934
1060
|
const remoteHost = "keyId" in reason && reason.keyId != null ? getRemoteHost(reason.keyId) : void 0;
|
|
935
1061
|
getFederationMetrics(parameters.meterProvider).recordSignatureVerificationFailure(reason.type, remoteHost);
|
|
@@ -1007,7 +1133,15 @@ async function handleInboxInternal(request, parameters, span) {
|
|
|
1007
1133
|
}
|
|
1008
1134
|
httpSigKey = verification.key;
|
|
1009
1135
|
}
|
|
1010
|
-
|
|
1136
|
+
try {
|
|
1137
|
+
activity = await Activity.fromJsonLd(jsonWithoutSig, {
|
|
1138
|
+
...ctx,
|
|
1139
|
+
contextLoader: wrapContextLoaderForJsonLd(ctx.contextLoader)
|
|
1140
|
+
});
|
|
1141
|
+
} catch (error) {
|
|
1142
|
+
if (!isPermanentActivityParseError(error)) throw error;
|
|
1143
|
+
return await respondInvalidActivity(error);
|
|
1144
|
+
}
|
|
1011
1145
|
}
|
|
1012
1146
|
if (activity.id != null) span.setAttribute("activitypub.activity.id", activity.id.href);
|
|
1013
1147
|
span.setAttribute("activitypub.activity.type", getTypeId(activity).href);
|
|
@@ -1019,6 +1153,7 @@ async function handleInboxInternal(request, parameters, span) {
|
|
|
1019
1153
|
"http_signatures.key_id": httpSigKey?.id?.href ?? ""
|
|
1020
1154
|
});
|
|
1021
1155
|
if (httpSigKey != null && !await doesActorOwnKey(activity, httpSigKey, ctx)) {
|
|
1156
|
+
if (deferredLdSignatureError != null) throw deferredLdSignatureError;
|
|
1022
1157
|
getFederationMetrics(parameters.meterProvider).recordSignatureVerificationFailure("actorKeyMismatch", httpSigKey.id == null ? void 0 : getRemoteHost(httpSigKey.id));
|
|
1023
1158
|
logger.error("The signer ({keyId}) and the actor ({actorId}) do not match.", {
|
|
1024
1159
|
activity: json,
|
|
@@ -1045,10 +1180,14 @@ async function handleInboxInternal(request, parameters, span) {
|
|
|
1045
1180
|
const routeResult = await routeActivity({
|
|
1046
1181
|
context: ctx,
|
|
1047
1182
|
json,
|
|
1183
|
+
originalJson: json,
|
|
1184
|
+
normalizedActivity: hasLdSignature && compactedJson !== json ? compactedJson : void 0,
|
|
1185
|
+
ldSignatureVerified: hasLdSignature ? ldSigVerified : void 0,
|
|
1048
1186
|
activity,
|
|
1049
1187
|
recipient,
|
|
1050
1188
|
inboxListeners,
|
|
1051
1189
|
inboxContextFactory,
|
|
1190
|
+
listenerInboxContextFactory: ldSigVerified ? inboxContextFactory[rawInboxContextFactorySymbol] : void 0,
|
|
1052
1191
|
inboxErrorHandler,
|
|
1053
1192
|
kv,
|
|
1054
1193
|
kvPrefixes,
|
|
@@ -1175,8 +1314,8 @@ var CustomCollectionHandler = class {
|
|
|
1175
1314
|
* @param CollectionPage The CollectionPage constructor.
|
|
1176
1315
|
* @param filterPredicate Optional filter predicate for items.
|
|
1177
1316
|
*/
|
|
1178
|
-
constructor(name$
|
|
1179
|
-
this.name = name$
|
|
1317
|
+
constructor(name$2, values, context, callbacks, tracerProvider = trace.getTracerProvider(), Collection, CollectionPage, filterPredicate) {
|
|
1318
|
+
this.name = name$2;
|
|
1180
1319
|
this.values = values;
|
|
1181
1320
|
this.context = context;
|
|
1182
1321
|
this.callbacks = callbacks;
|
|
@@ -1600,22 +1739,72 @@ const logger = getLogger([
|
|
|
1600
1739
|
* @returns The response to the request.
|
|
1601
1740
|
*/
|
|
1602
1741
|
async function handleWebFinger(request, options) {
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
});
|
|
1614
|
-
throw error;
|
|
1615
|
-
} finally {
|
|
1616
|
-
span.end();
|
|
1742
|
+
const meterProvider = options.meterProvider;
|
|
1743
|
+
const start = meterProvider == null ? 0 : performance.now();
|
|
1744
|
+
const scheme = computeResourceScheme(options.context.url.searchParams.get("resource"));
|
|
1745
|
+
let notFoundResponse;
|
|
1746
|
+
const wrappedOptions = {
|
|
1747
|
+
...options,
|
|
1748
|
+
async onNotFound(req) {
|
|
1749
|
+
const r = await options.onNotFound(req);
|
|
1750
|
+
notFoundResponse = r;
|
|
1751
|
+
return r;
|
|
1617
1752
|
}
|
|
1618
|
-
}
|
|
1753
|
+
};
|
|
1754
|
+
let response;
|
|
1755
|
+
try {
|
|
1756
|
+
if (options.tracer == null) response = await handleWebFingerInternal(request, wrappedOptions);
|
|
1757
|
+
else response = await options.tracer.startActiveSpan("webfinger.handle", { kind: SpanKind.SERVER }, async (span) => {
|
|
1758
|
+
try {
|
|
1759
|
+
const inner = await handleWebFingerInternal(request, wrappedOptions);
|
|
1760
|
+
span.setStatus({ code: inner.ok ? SpanStatusCode.UNSET : SpanStatusCode.ERROR });
|
|
1761
|
+
return inner;
|
|
1762
|
+
} catch (error) {
|
|
1763
|
+
span.setStatus({
|
|
1764
|
+
code: SpanStatusCode.ERROR,
|
|
1765
|
+
message: String(error)
|
|
1766
|
+
});
|
|
1767
|
+
throw error;
|
|
1768
|
+
} finally {
|
|
1769
|
+
span.end();
|
|
1770
|
+
}
|
|
1771
|
+
});
|
|
1772
|
+
return response;
|
|
1773
|
+
} finally {
|
|
1774
|
+
if (meterProvider != null) recordWebFingerHandle(meterProvider, {
|
|
1775
|
+
durationMs: Math.max(0, performance.now() - start),
|
|
1776
|
+
result: classifyWebFingerHandleResult(response, notFoundResponse),
|
|
1777
|
+
scheme,
|
|
1778
|
+
statusCode: response?.status
|
|
1779
|
+
});
|
|
1780
|
+
}
|
|
1781
|
+
}
|
|
1782
|
+
const WEBFINGER_HANDLE_SCHEME_WHITELIST = new Set([
|
|
1783
|
+
"acct",
|
|
1784
|
+
"http",
|
|
1785
|
+
"https",
|
|
1786
|
+
"mailto"
|
|
1787
|
+
]);
|
|
1788
|
+
function isAllowedResourceScheme(scheme) {
|
|
1789
|
+
return WEBFINGER_HANDLE_SCHEME_WHITELIST.has(scheme);
|
|
1790
|
+
}
|
|
1791
|
+
function computeResourceScheme(resource) {
|
|
1792
|
+
if (resource == null) return void 0;
|
|
1793
|
+
const colon = resource.indexOf(":");
|
|
1794
|
+
if (colon <= 0) return void 0;
|
|
1795
|
+
const candidate = resource.substring(0, colon).toLowerCase();
|
|
1796
|
+
return isAllowedResourceScheme(candidate) ? candidate : "other";
|
|
1797
|
+
}
|
|
1798
|
+
function classifyWebFingerHandleResult(response, notFoundResponse) {
|
|
1799
|
+
if (response == null) return "error";
|
|
1800
|
+
if (notFoundResponse != null && response === notFoundResponse) return "not_found";
|
|
1801
|
+
switch (response.status) {
|
|
1802
|
+
case 200: return "resolved";
|
|
1803
|
+
case 400: return "invalid";
|
|
1804
|
+
case 404: return "not_found";
|
|
1805
|
+
case 410: return "tombstoned";
|
|
1806
|
+
default: return "error";
|
|
1807
|
+
}
|
|
1619
1808
|
}
|
|
1620
1809
|
async function handleWebFingerInternal(request, { context, host, actorDispatcher, actorHandleMapper, actorAliasMapper, onNotFound, span, webFingerLinksDispatcher }) {
|
|
1621
1810
|
if (actorDispatcher == null) {
|
|
@@ -1723,6 +1912,18 @@ async function handleWebFingerInternal(request, { context, host, actorDispatcher
|
|
|
1723
1912
|
}
|
|
1724
1913
|
//#endregion
|
|
1725
1914
|
//#region src/federation/middleware.ts
|
|
1915
|
+
function isRemoteContextLoadingFailure(error) {
|
|
1916
|
+
return error instanceof Error && typeof error.details === "object" && error.details != null && error.details.code === "loading remote context failed";
|
|
1917
|
+
}
|
|
1918
|
+
function isPermanentRemoteContextError(error) {
|
|
1919
|
+
if (!(error instanceof Error) || error.name !== "jsonld.InvalidUrl") return false;
|
|
1920
|
+
const details = error.details;
|
|
1921
|
+
if (details?.code === "invalid remote context") return true;
|
|
1922
|
+
return isRemoteContextLoadingFailure(error) && typeof details?.url === "string" && !URL.canParse(details.url) && isClearlyMalformedContextReference(details.url);
|
|
1923
|
+
}
|
|
1924
|
+
function isPermanentInboxParseError(error) {
|
|
1925
|
+
return error instanceof Error && (error.name === "UnsafeJsonLdError" || error instanceof InvalidContextReferenceError || isPermanentRemoteContextError(error) || error.name === "jsonld.SyntaxError" && !isRemoteContextLoadingFailure(error)) || error instanceof TypeError && (/^(Invalid JSON-LD:|Invalid type:|Unexpected type:)/.test(error.message) || isInvalidUrlTypeError(error));
|
|
1926
|
+
}
|
|
1726
1927
|
/**
|
|
1727
1928
|
* Create a new {@link Federation} instance.
|
|
1728
1929
|
* @param parameters Parameters for initializing the instance.
|
|
@@ -2225,78 +2426,37 @@ var FederationImpl = class extends FederationBuilderImpl {
|
|
|
2225
2426
|
const identity = await this.sharedInboxKeyDispatcher(context);
|
|
2226
2427
|
if (identity != null) context = this.#createContext(baseUrl, ctxData, { documentLoader: "identifier" in identity || "username" in identity ? await context.getDocumentLoader(identity) : context.getDocumentLoader(identity) });
|
|
2227
2428
|
}
|
|
2228
|
-
|
|
2229
|
-
|
|
2230
|
-
|
|
2231
|
-
|
|
2232
|
-
|
|
2233
|
-
const cacheKey = activity.id == null ? null : [
|
|
2234
|
-
...this.kvPrefixes.activityIdempotence,
|
|
2235
|
-
context.origin,
|
|
2236
|
-
activity.id.href
|
|
2237
|
-
];
|
|
2238
|
-
if (cacheKey != null) {
|
|
2239
|
-
if (await this.kv.get(cacheKey) === true) {
|
|
2240
|
-
logger.debug("Activity {activityId} has already been processed.", {
|
|
2241
|
-
activityId: activity.id?.href,
|
|
2242
|
-
activity: message.activity,
|
|
2243
|
-
recipient: message.identifier
|
|
2244
|
-
});
|
|
2245
|
-
recordInboxActivity(this.meterProvider, "rejected", activityType);
|
|
2246
|
-
return;
|
|
2247
|
-
}
|
|
2248
|
-
}
|
|
2249
|
-
await this._getTracer().startActiveSpan("activitypub.dispatch_inbox_listener", { kind: SpanKind.INTERNAL }, async (span) => {
|
|
2250
|
-
const dispatched = this.inboxListeners?.dispatchWithClass(activity);
|
|
2251
|
-
if (dispatched == null) {
|
|
2252
|
-
logger.error("Unsupported activity type:\n{activity}", {
|
|
2253
|
-
activityId: activity.id?.href,
|
|
2254
|
-
activity: message.activity,
|
|
2255
|
-
recipient: message.identifier,
|
|
2256
|
-
trial: message.attempt
|
|
2257
|
-
});
|
|
2258
|
-
span.setStatus({
|
|
2259
|
-
code: SpanStatusCode.ERROR,
|
|
2260
|
-
message: `Unsupported activity type: ${activityType}`
|
|
2261
|
-
});
|
|
2262
|
-
recordInboxActivity(this.meterProvider, "rejected", activityType);
|
|
2263
|
-
span.end();
|
|
2264
|
-
return;
|
|
2265
|
-
}
|
|
2266
|
-
const { class: cls, listener } = dispatched;
|
|
2267
|
-
span.updateName(`activitypub.dispatch_inbox_listener ${cls.name}`);
|
|
2268
|
-
try {
|
|
2269
|
-
const started = performance.now();
|
|
2270
|
-
try {
|
|
2271
|
-
await listener(context.toInboxContext(message.identifier, message.activity, activity.id?.href, activityType), activity);
|
|
2272
|
-
} finally {
|
|
2273
|
-
getFederationMetrics(this.meterProvider).recordInboxProcessingDuration(activityType, getDurationMs(started));
|
|
2274
|
-
}
|
|
2275
|
-
recordInboxActivity(this.meterProvider, "processed", activityType);
|
|
2276
|
-
} catch (error) {
|
|
2429
|
+
await this._getTracer().startActiveSpan("activitypub.dispatch_inbox_listener", { kind: SpanKind.INTERNAL }, async (listenerSpan) => {
|
|
2430
|
+
let activity = null;
|
|
2431
|
+
let cacheKey = null;
|
|
2432
|
+
let activityType;
|
|
2433
|
+
const reportInboxError = async (error) => {
|
|
2277
2434
|
try {
|
|
2278
2435
|
await this.inboxErrorHandler?.(context, error);
|
|
2279
2436
|
} catch (error) {
|
|
2280
2437
|
logger.error("An unexpected error occurred in inbox error handler:\n{error}", {
|
|
2281
2438
|
error,
|
|
2282
2439
|
trial: message.attempt,
|
|
2283
|
-
activityId: activity
|
|
2440
|
+
activityId: activity?.id?.href,
|
|
2284
2441
|
activity: message.activity,
|
|
2285
2442
|
recipient: message.identifier
|
|
2286
2443
|
});
|
|
2287
2444
|
}
|
|
2445
|
+
};
|
|
2446
|
+
const handleRetriableFailure = async (error) => {
|
|
2447
|
+
await reportInboxError(error);
|
|
2288
2448
|
if (this.inboxQueue?.nativeRetrial) {
|
|
2289
2449
|
logger.error("Failed to process the incoming activity {activityId}; backend will handle retry:\n{error}", {
|
|
2290
2450
|
error,
|
|
2291
|
-
activityId: activity
|
|
2451
|
+
activityId: activity?.id?.href,
|
|
2292
2452
|
activity: message.activity,
|
|
2293
2453
|
recipient: message.identifier
|
|
2294
2454
|
});
|
|
2295
|
-
|
|
2455
|
+
listenerSpan.setStatus({
|
|
2296
2456
|
code: SpanStatusCode.ERROR,
|
|
2297
2457
|
message: String(error)
|
|
2298
2458
|
});
|
|
2299
|
-
|
|
2459
|
+
listenerSpan.end();
|
|
2300
2460
|
throw error;
|
|
2301
2461
|
}
|
|
2302
2462
|
const delay = this.inboxRetryPolicy({
|
|
@@ -2307,20 +2467,27 @@ var FederationImpl = class extends FederationBuilderImpl {
|
|
|
2307
2467
|
logger.error("Failed to process the incoming activity {activityId} (attempt #{attempt}); retry...:\n{error}", {
|
|
2308
2468
|
error,
|
|
2309
2469
|
attempt: message.attempt,
|
|
2310
|
-
activityId: activity
|
|
2470
|
+
activityId: activity?.id?.href,
|
|
2311
2471
|
activity: message.activity,
|
|
2312
2472
|
recipient: message.identifier
|
|
2313
2473
|
});
|
|
2474
|
+
if (this.inboxQueue == null) {
|
|
2475
|
+
listenerSpan.setStatus({
|
|
2476
|
+
code: SpanStatusCode.ERROR,
|
|
2477
|
+
message: String(error)
|
|
2478
|
+
});
|
|
2479
|
+
listenerSpan.end();
|
|
2480
|
+
throw error;
|
|
2481
|
+
}
|
|
2314
2482
|
const retryMessage = {
|
|
2315
2483
|
...message,
|
|
2316
2484
|
attempt: message.attempt + 1
|
|
2317
2485
|
};
|
|
2318
|
-
|
|
2319
|
-
if (
|
|
2320
|
-
await inboxQueue.enqueue(retryMessage, { delay: Temporal.Duration.compare(delay, { seconds: 0 }) < 0 ? Temporal.Duration.from({ seconds: 0 }) : delay });
|
|
2486
|
+
await this.inboxQueue.enqueue(retryMessage, { delay: Temporal.Duration.compare(delay, { seconds: 0 }) < 0 ? Temporal.Duration.from({ seconds: 0 }) : delay });
|
|
2487
|
+
if (activityType != null) {
|
|
2321
2488
|
getFederationMetrics(this.meterProvider).recordQueueTaskEnqueued({
|
|
2322
2489
|
role: "inbox",
|
|
2323
|
-
queue: inboxQueue,
|
|
2490
|
+
queue: this.inboxQueue,
|
|
2324
2491
|
activityType
|
|
2325
2492
|
}, retryMessage.attempt);
|
|
2326
2493
|
recordInboxActivity(this.meterProvider, "retried", activityType);
|
|
@@ -2328,26 +2495,137 @@ var FederationImpl = class extends FederationBuilderImpl {
|
|
|
2328
2495
|
} else {
|
|
2329
2496
|
logger.error("Failed to process the incoming activity {activityId} after {trial} attempts; giving up:\n{error}", {
|
|
2330
2497
|
error,
|
|
2331
|
-
activityId: activity
|
|
2498
|
+
activityId: activity?.id?.href,
|
|
2332
2499
|
activity: message.activity,
|
|
2333
2500
|
recipient: message.identifier
|
|
2334
2501
|
});
|
|
2335
|
-
recordInboxActivity(this.meterProvider, "abandoned", activityType);
|
|
2502
|
+
if (activityType != null) recordInboxActivity(this.meterProvider, "abandoned", activityType);
|
|
2336
2503
|
}
|
|
2337
|
-
|
|
2504
|
+
listenerSpan.setStatus({
|
|
2338
2505
|
code: SpanStatusCode.ERROR,
|
|
2339
2506
|
message: String(error)
|
|
2340
2507
|
});
|
|
2341
|
-
|
|
2508
|
+
listenerSpan.end();
|
|
2509
|
+
};
|
|
2510
|
+
let dispatched;
|
|
2511
|
+
let parseInput = void 0;
|
|
2512
|
+
let parseContextLoader = context.contextLoader;
|
|
2513
|
+
try {
|
|
2514
|
+
const hasSignatureField = hasSignature(message.activity);
|
|
2515
|
+
const shouldParseFromNormalizedSignedPayload = message.ldSignatureVerified === true || message.normalizedActivity != null || message.ldSignatureVerified == null && hasSignatureField;
|
|
2516
|
+
const parseContext = hasSignatureField ? {
|
|
2517
|
+
...context,
|
|
2518
|
+
contextLoader: getNormalizationContextLoader(context.contextLoader)
|
|
2519
|
+
} : {
|
|
2520
|
+
...context,
|
|
2521
|
+
contextLoader: wrapContextLoaderForJsonLd(context.contextLoader)
|
|
2522
|
+
};
|
|
2523
|
+
parseContextLoader = parseContext.contextLoader;
|
|
2524
|
+
let normalizedActivity;
|
|
2525
|
+
if (shouldParseFromNormalizedSignedPayload) {
|
|
2526
|
+
normalizedActivity = message.normalizedActivity ?? await compactJsonLd(message.activity, context.contextLoader);
|
|
2527
|
+
assertSafeJsonLd(normalizedActivity);
|
|
2528
|
+
}
|
|
2529
|
+
parseInput = shouldParseFromNormalizedSignedPayload ? detachSignature(normalizedActivity) : hasSignatureField ? detachSignature(message.activity) : message.activity;
|
|
2530
|
+
activity = await Activity.fromJsonLd(parseInput, parseContext);
|
|
2531
|
+
activityType = getTypeId(activity).href;
|
|
2532
|
+
span.setAttribute("activitypub.activity.type", activityType);
|
|
2533
|
+
listenerSpan.setAttribute("activitypub.activity.type", activityType);
|
|
2534
|
+
onActivityType?.(activityType);
|
|
2535
|
+
if (activity.id != null) {
|
|
2536
|
+
span.setAttribute("activitypub.activity.id", activity.id.href);
|
|
2537
|
+
listenerSpan.setAttribute("activitypub.activity.id", activity.id.href);
|
|
2538
|
+
}
|
|
2539
|
+
cacheKey = activity.id == null ? null : [
|
|
2540
|
+
...this.kvPrefixes.activityIdempotence,
|
|
2541
|
+
context.origin,
|
|
2542
|
+
activity.id.href
|
|
2543
|
+
];
|
|
2544
|
+
if (cacheKey != null) {
|
|
2545
|
+
if (await this.kv.get(cacheKey) === true) {
|
|
2546
|
+
logger.debug("Activity {activityId} has already been processed.", {
|
|
2547
|
+
activityId: activity.id?.href,
|
|
2548
|
+
activity: message.activity,
|
|
2549
|
+
recipient: message.identifier
|
|
2550
|
+
});
|
|
2551
|
+
recordInboxActivity(this.meterProvider, "rejected", activityType);
|
|
2552
|
+
listenerSpan.end();
|
|
2553
|
+
return;
|
|
2554
|
+
}
|
|
2555
|
+
}
|
|
2556
|
+
dispatched = this.inboxListeners?.dispatchWithClass(activity);
|
|
2557
|
+
} catch (error) {
|
|
2558
|
+
if (activity == null && error instanceof RangeError && await hasMalformedKnownTemporalLiteral(parseInput, parseContextLoader)) {
|
|
2559
|
+
await reportInboxError(error);
|
|
2560
|
+
logger.error("Failed to parse the queued incoming activity {activityId}:\n{error}", {
|
|
2561
|
+
error,
|
|
2562
|
+
trial: message.attempt,
|
|
2563
|
+
activityId: null,
|
|
2564
|
+
activity: message.activity,
|
|
2565
|
+
recipient: message.identifier
|
|
2566
|
+
});
|
|
2567
|
+
listenerSpan.setStatus({
|
|
2568
|
+
code: SpanStatusCode.ERROR,
|
|
2569
|
+
message: String(error)
|
|
2570
|
+
});
|
|
2571
|
+
listenerSpan.end();
|
|
2572
|
+
return;
|
|
2573
|
+
}
|
|
2574
|
+
if (isPermanentInboxParseError(error)) {
|
|
2575
|
+
await reportInboxError(error);
|
|
2576
|
+
logger.error("Failed to parse the queued incoming activity {activityId}:\n{error}", {
|
|
2577
|
+
error,
|
|
2578
|
+
trial: message.attempt,
|
|
2579
|
+
activityId: activity?.id?.href,
|
|
2580
|
+
activity: message.activity,
|
|
2581
|
+
recipient: message.identifier
|
|
2582
|
+
});
|
|
2583
|
+
listenerSpan.setStatus({
|
|
2584
|
+
code: SpanStatusCode.ERROR,
|
|
2585
|
+
message: String(error)
|
|
2586
|
+
});
|
|
2587
|
+
listenerSpan.end();
|
|
2588
|
+
return;
|
|
2589
|
+
}
|
|
2590
|
+
await handleRetriableFailure(error);
|
|
2591
|
+
return;
|
|
2592
|
+
}
|
|
2593
|
+
if (dispatched == null) {
|
|
2594
|
+
logger.error("Unsupported activity type:\n{activity}", {
|
|
2595
|
+
activityId: activity.id?.href,
|
|
2596
|
+
activity: message.activity,
|
|
2597
|
+
recipient: message.identifier,
|
|
2598
|
+
trial: message.attempt
|
|
2599
|
+
});
|
|
2600
|
+
listenerSpan.setStatus({
|
|
2601
|
+
code: SpanStatusCode.ERROR,
|
|
2602
|
+
message: `Unsupported activity type: ${activityType}`
|
|
2603
|
+
});
|
|
2604
|
+
recordInboxActivity(this.meterProvider, "rejected", activityType);
|
|
2605
|
+
listenerSpan.end();
|
|
2606
|
+
return;
|
|
2607
|
+
}
|
|
2608
|
+
const { class: cls, listener } = dispatched;
|
|
2609
|
+
listenerSpan.updateName(`activitypub.dispatch_inbox_listener ${cls.name}`);
|
|
2610
|
+
try {
|
|
2611
|
+
const started = performance.now();
|
|
2612
|
+
try {
|
|
2613
|
+
await listener(context.toInboxContext(message.identifier, message.activity, activity.id?.href, activityType), activity);
|
|
2614
|
+
} finally {
|
|
2615
|
+
getFederationMetrics(this.meterProvider).recordInboxProcessingDuration(activityType, getDurationMs(started));
|
|
2616
|
+
}
|
|
2617
|
+
recordInboxActivity(this.meterProvider, "processed", activityType);
|
|
2618
|
+
} catch (error) {
|
|
2619
|
+
await handleRetriableFailure(error);
|
|
2342
2620
|
return;
|
|
2343
2621
|
}
|
|
2344
2622
|
if (cacheKey != null) await this.kv.set(cacheKey, true, { ttl: Temporal.Duration.from({ days: 1 }) });
|
|
2345
2623
|
logger.info("Activity {activityId} has been processed.", {
|
|
2346
|
-
activityId: activity
|
|
2624
|
+
activityId: activity?.id?.href,
|
|
2347
2625
|
activity: message.activity,
|
|
2348
2626
|
recipient: message.identifier
|
|
2349
2627
|
});
|
|
2350
|
-
|
|
2628
|
+
listenerSpan.end();
|
|
2351
2629
|
});
|
|
2352
2630
|
}
|
|
2353
2631
|
startQueue(contextData, options = {}) {
|
|
@@ -2641,7 +2919,8 @@ var FederationImpl = class extends FederationBuilderImpl {
|
|
|
2641
2919
|
actorAliasMapper: this.actorCallbacks?.aliasMapper,
|
|
2642
2920
|
webFingerLinksDispatcher: this.webFingerLinksDispatcher,
|
|
2643
2921
|
onNotFound,
|
|
2644
|
-
tracer
|
|
2922
|
+
tracer,
|
|
2923
|
+
meterProvider: this._meterProvider
|
|
2645
2924
|
});
|
|
2646
2925
|
case "nodeInfoJrd": return await handleNodeInfoJrd(request, context);
|
|
2647
2926
|
case "nodeInfo": return await handleNodeInfo(request, {
|
|
@@ -2727,16 +3006,18 @@ var FederationImpl = class extends FederationBuilderImpl {
|
|
|
2727
3006
|
onNotFound
|
|
2728
3007
|
});
|
|
2729
3008
|
context = this.#createContext(request, contextData, { documentLoader: await context.getDocumentLoader({ identifier: route.values.identifier }) });
|
|
2730
|
-
case "sharedInbox":
|
|
3009
|
+
case "sharedInbox": {
|
|
2731
3010
|
if (routeName !== "inbox" && this.sharedInboxKeyDispatcher != null) {
|
|
2732
3011
|
const identity = await this.sharedInboxKeyDispatcher(context);
|
|
2733
3012
|
if (identity != null) context = this.#createContext(request, contextData, { documentLoader: "identifier" in identity || "username" in identity ? await context.getDocumentLoader(identity) : context.getDocumentLoader(identity) });
|
|
2734
3013
|
}
|
|
2735
3014
|
if (!this.manuallyStartQueue) this._startQueueInternal(contextData);
|
|
3015
|
+
const inboxContextFactory = context.toInboxContext.bind(context);
|
|
3016
|
+
inboxContextFactory[rawInboxContextFactorySymbol] = context.toInboxContext.bind(context);
|
|
2736
3017
|
return await handleInbox(request, {
|
|
2737
3018
|
recipient: route.values.identifier ?? null,
|
|
2738
3019
|
context,
|
|
2739
|
-
inboxContextFactory
|
|
3020
|
+
inboxContextFactory,
|
|
2740
3021
|
kv: this.kv,
|
|
2741
3022
|
kvPrefixes: this.kvPrefixes,
|
|
2742
3023
|
queue: this.inboxQueue,
|
|
@@ -2752,6 +3033,7 @@ var FederationImpl = class extends FederationBuilderImpl {
|
|
|
2752
3033
|
tracerProvider: this.tracerProvider,
|
|
2753
3034
|
idempotencyStrategy: this.idempotencyStrategy
|
|
2754
3035
|
});
|
|
3036
|
+
}
|
|
2755
3037
|
case "following": return await handleCollection(request, {
|
|
2756
3038
|
name: "following",
|
|
2757
3039
|
identifier: route.values.identifier,
|
|
@@ -3200,6 +3482,7 @@ var ContextImpl = class ContextImpl {
|
|
|
3200
3482
|
...options,
|
|
3201
3483
|
userAgent: options.userAgent ?? this.federation.userAgent,
|
|
3202
3484
|
tracerProvider: options.tracerProvider ?? this.tracerProvider,
|
|
3485
|
+
meterProvider: options.meterProvider ?? this.federation._meterProvider,
|
|
3203
3486
|
allowPrivateAddress: this.federation.allowPrivateAddress
|
|
3204
3487
|
});
|
|
3205
3488
|
}
|
|
@@ -3484,6 +3767,7 @@ var ContextImpl = class ContextImpl {
|
|
|
3484
3767
|
const routeResult = await routeActivity({
|
|
3485
3768
|
context: this,
|
|
3486
3769
|
json,
|
|
3770
|
+
ldSignatureVerified: false,
|
|
3487
3771
|
activity,
|
|
3488
3772
|
recipient,
|
|
3489
3773
|
inboxListeners: this.federation.inboxListeners,
|
|
@@ -3772,6 +4056,14 @@ async function forwardActivityInternal(ctx, loggerCategory, forwarder, recipient
|
|
|
3772
4056
|
}
|
|
3773
4057
|
var InboxContextImpl = class InboxContextImpl extends ContextImpl {
|
|
3774
4058
|
recipient;
|
|
4059
|
+
/**
|
|
4060
|
+
* The original received activity payload.
|
|
4061
|
+
*
|
|
4062
|
+
* Fedify may normalize a Linked Data Signature payload internally for safe
|
|
4063
|
+
* parsing, but forwarding must keep the sender's payload unchanged so
|
|
4064
|
+
* third-party signatures/proofs remain intact.
|
|
4065
|
+
* @internal
|
|
4066
|
+
*/
|
|
3775
4067
|
activity;
|
|
3776
4068
|
activityId;
|
|
3777
4069
|
activityType;
|