@fedify/fedify 2.4.0-pr.936.41 → 2.5.0-dev.2271
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 +50 -39
- package/dist/{assert-CJ1KXcvv.mjs → assert-D1Eafmd_.mjs} +1 -1
- package/dist/{assert_equals-BV7oLMxm.mjs → assert_equals-Z6IcEbWU.mjs} +2 -28
- package/dist/assert_exists-cffSpJ7X.mjs +28 -0
- package/dist/assert_false-CPdRnc3E.mjs +23 -0
- package/dist/assert_greater_or_equal-BlafOF4-.mjs +31 -0
- package/dist/{assert_instance_of-Q9KTBnrf.mjs → assert_instance_of-mrXWWNY-.mjs} +1 -1
- package/dist/{assert_not_equals-EPJPoxuf.mjs → assert_not_equals-CpPK2t89.mjs} +2 -1
- package/dist/assert_rejects-CTptdYck.mjs +39 -0
- package/dist/{assert_strict_equals-Ec20AUwa.mjs → assert_strict_equals-CYvBmkpC.mjs} +2 -1
- package/dist/{assert_throws-NiFfqbnH.mjs → assert_throws-BYITOvPO.mjs} +2 -1
- package/dist/assertion_error-B-cx0fFA.mjs +31 -0
- package/dist/audience-CHQ-FlLD.mjs +95 -0
- package/dist/{builder-DonuxRl_.mjs → builder-Bda6LK7A.mjs} +169 -14
- package/dist/{circuit-breaker-CBGyz5F7.mjs → circuit-breaker-XJ53md4S.mjs} +224 -17
- package/dist/circuit-breaker-test-utils-DW6YC_Pe.mjs +14 -0
- package/dist/{client-CjF5IYQS.mjs → client-CEnNz8O7.mjs} +144 -8
- package/dist/{codec-BBga-FWI.mjs → codec-C6rTrpIp.mjs} +26 -3
- package/dist/{collection-BIhlldqK.mjs → collection-CnnP1UAv.mjs} +1 -1
- package/dist/compat/mod.cjs +1 -1
- package/dist/compat/mod.d.cts +1 -1
- package/dist/compat/mod.d.ts +1 -1
- package/dist/compat/mod.js +1 -1
- package/dist/compat/outgoing-jsonld.test.mjs +71 -3
- package/dist/compat/public-audience.test.mjs +14 -5
- package/dist/compat/secured-document.test.d.mts +2 -0
- package/dist/compat/secured-document.test.mjs +58 -0
- package/dist/compat/transformers.test.mjs +5 -5
- package/dist/compound-proof-B-9mXxFW.mjs +989 -0
- package/dist/{context-Zii1f5xS.d.ts → context-7NyiqIcg.d.ts} +1600 -25
- package/dist/{context-BJXxPElm.mjs → context-8Tu9kea0.mjs} +21 -63
- package/dist/{context-lxX6I7as.d.cts → context-tsYPJ2Ew.d.cts} +1600 -25
- package/dist/{deno-BHzKn9Ig.mjs → deno-oR5aJKpU.mjs} +1 -1
- package/dist/dist-DJQM4u7i.mjs +189 -0
- package/dist/docloader-DBoYbvYW.mjs +87 -0
- package/dist/{esm-yYQfPhIY.mjs → esm-BHXXx4Wx.mjs} +13 -6
- package/dist/federation/audience.test.d.mts +2 -0
- package/dist/federation/audience.test.mjs +130 -0
- package/dist/federation/builder.test.mjs +54 -5
- package/dist/federation/circuit-breaker.test.mjs +838 -7
- package/dist/federation/collection.test.mjs +3 -3
- package/dist/federation/compound-fanout.test.d.mts +2 -0
- package/dist/federation/compound-fanout.test.mjs +228 -0
- package/dist/federation/handler-compound-proof.test.d.mts +2 -0
- package/dist/federation/handler-compound-proof.test.mjs +384 -0
- package/dist/federation/handler.test.mjs +385 -15
- package/dist/federation/hashlink-media.test.d.mts +2 -0
- package/dist/federation/hashlink-media.test.mjs +422 -0
- package/dist/federation/idempotency.test.mjs +6 -6
- package/dist/federation/inbox-report.test.d.mts +2 -0
- package/dist/federation/inbox-report.test.mjs +475 -0
- package/dist/federation/inbox.test.mjs +3 -3
- package/dist/federation/keycache.test.mjs +215 -14
- package/dist/federation/kv.test.mjs +3 -3
- package/dist/federation/metrics.test.mjs +5 -5
- package/dist/federation/middleware.test.mjs +967 -35
- package/dist/federation/mod.cjs +18 -261
- package/dist/federation/mod.d.cts +5 -5
- package/dist/federation/mod.d.ts +5 -5
- package/dist/federation/mod.js +279 -1
- package/dist/federation/mq.test.mjs +123 -47
- package/dist/federation/negotiation.test.mjs +4 -4
- package/dist/federation/outgoing-proof.test.d.mts +2 -0
- package/dist/federation/outgoing-proof.test.mjs +931 -0
- package/dist/federation/portable-actor.test.d.mts +2 -0
- package/dist/federation/portable-actor.test.mjs +496 -0
- package/dist/federation/portable-chain.test.d.mts +2 -0
- package/dist/federation/portable-chain.test.mjs +370 -0
- package/dist/federation/portable-collection.test.d.mts +2 -0
- package/dist/federation/portable-collection.test.mjs +832 -0
- package/dist/federation/portable-delivery.test.d.mts +2 -0
- package/dist/federation/portable-delivery.test.mjs +746 -0
- package/dist/federation/portable-gateway-keys.test.d.mts +2 -0
- package/dist/federation/portable-gateway-keys.test.mjs +686 -0
- package/dist/federation/portable-inbox.test.d.mts +2 -0
- package/dist/federation/portable-inbox.test.mjs +1535 -0
- package/dist/federation/portable-parse-uri.test.d.mts +2 -0
- package/dist/federation/portable-parse-uri.test.mjs +348 -0
- package/dist/federation/portable-route-activity.test.d.mts +2 -0
- package/dist/federation/portable-route-activity.test.mjs +259 -0
- package/dist/federation/portable.test.d.mts +2 -0
- package/dist/federation/portable.test.mjs +1072 -0
- package/dist/federation/retry.test.mjs +4 -3
- package/dist/federation/router.test.mjs +9 -159
- package/dist/federation/send.test.mjs +245 -27
- package/dist/federation/tasks/codec.test.mjs +4 -4
- package/dist/federation/tasks/enqueue.test.mjs +42 -4
- package/dist/federation/tasks/tasks.test.mjs +7 -6
- package/dist/federation/temporal.test.mjs +4 -4
- package/dist/federation/webfinger.test.mjs +275 -12
- package/dist/{middleware-FrSMAYEx.cjs → federation-CkZiAcWY.cjs} +8640 -3722
- package/dist/http-B5JT115n.cjs +10630 -0
- package/dist/http-DydWCB7R.js +6527 -0
- package/dist/{http-CPVMUgsW.mjs → http-siciWkmx.mjs} +397 -168
- package/dist/key-BspPiXqn.mjs +951 -0
- package/dist/key-C1nv7_jD.d.ts +1125 -0
- package/dist/key-DYxuyd-s.d.cts +1125 -0
- package/dist/key-owner-evidence-APB6NpSk.mjs +108 -0
- package/dist/keycache-Da8A8mZa.mjs +200 -0
- package/dist/{kv-D6hNiMTK.d.ts → kv-B2mzK5tl.d.cts} +3 -2
- package/dist/{kv-gJ8LYbxX.d.cts → kv-B2mzK5tl.d.ts} +3 -2
- package/dist/kv-DBRWVx3Q.mjs +104 -0
- package/dist/{kv-cache-KyGqJqSp.mjs → kv-cache-BF1hnW65.mjs} +1 -1
- package/dist/{kv-cache-5bnX3-Jq.js → kv-cache-CTQr1tbn.js} +54 -16
- package/dist/{kv-cache-DQ29C4XL.cjs → kv-cache-DCac1nEU.cjs} +55 -19
- package/dist/{ld-Cj9F_5AP.mjs → ld-CzaP-hJl.mjs} +44 -12
- package/dist/map-local-context-conflict-DOYEEJ3u.mjs +204 -0
- package/dist/map-local-create-note-B0SeXJms.mjs +214 -0
- package/dist/{metrics-DMk-nTxI.mjs → metrics-CYg9yxBL.mjs} +3 -2
- package/dist/metrics-DVKYY6K1.d.cts +36 -0
- package/dist/metrics-DoRtI0qU.d.ts +36 -0
- package/dist/{middleware-Y-kpsp6S.js → middleware-CsDDXKuL.js} +5493 -1103
- package/dist/{middleware-CvwTUgnP.mjs → middleware-D7_LO-Vs.mjs} +3157 -272
- package/dist/{middleware-CEctxBN6.mjs → middleware-DWjrfpcz.mjs} +1 -1
- package/dist/{mod-gOlPDuT8.d.cts → mod-BojP2Bw3.d.cts} +1 -1
- package/dist/mod-CavhAgLw.d.cts +320 -0
- package/dist/mod-CtpF2BU5.d.ts +320 -0
- package/dist/{mod-BtEaIEcD.d.cts → mod-DEZXwV6J.d.ts} +10 -4
- package/dist/{mod-UMmveiO0.d.ts → mod-Dw-nPxQh.d.cts} +10 -4
- package/dist/{mod-DLc2Ot6f.d.ts → mod-Xj5P4s4d.d.ts} +1 -1
- package/dist/mod.cjs +40 -39
- package/dist/mod.d.cts +10 -11
- package/dist/mod.d.ts +10 -11
- package/dist/mod.js +9 -9
- package/dist/{mq-llHKivST.mjs → mq-Dm1Wmtm-.mjs} +6 -0
- package/dist/nodeinfo/client.test.mjs +139 -4
- package/dist/nodeinfo/handler.test.mjs +5 -5
- package/dist/nodeinfo/mod.cjs +1 -1
- package/dist/nodeinfo/mod.d.cts +1 -1
- package/dist/nodeinfo/mod.d.ts +1 -1
- package/dist/nodeinfo/mod.js +1 -1
- package/dist/nodeinfo/types.test.mjs +4 -4
- package/dist/otel/exporter.test.mjs +3 -3
- package/dist/otel/mod.cjs +0 -1
- package/dist/otel/mod.d.cts +1 -1
- package/dist/otel/mod.d.ts +1 -1
- package/dist/{outgoing-jsonld-URuqsICz.mjs → outgoing-jsonld-4IJqPYos.mjs} +12 -10
- package/dist/{owner-DEvZuyOE.d.cts → owner-BF0L9cMt.d.cts} +11 -0
- package/dist/{owner-CnngXDNJ.d.ts → owner-BSzr5-I8.d.ts} +11 -0
- package/dist/owner-DYgfTFHy.mjs +267 -0
- package/dist/portable-collection-BpsVsRov.js +670 -0
- package/dist/portable-collection-C74P1vtC.mjs +418 -0
- package/dist/portable-collection-DZd2bm7M.cjs +686 -0
- package/dist/portable-key-BIMLrAP4.mjs +549 -0
- package/dist/portable-key-id-Ca30-hOa.mjs +248 -0
- package/dist/proof-Dxy6ByqI.mjs +1299 -0
- package/dist/{public-audience-Cr5MsnMq.mjs → public-audience--klBIOk1.mjs} +15 -10
- package/dist/router-u5O5KCQr.mjs +156 -0
- package/dist/runtime/mod.cjs +1 -1
- package/dist/runtime/mod.js +1 -1
- package/dist/secured-document-CxzZZ9nL.mjs +35 -0
- package/dist/send-C4dDpAxm.mjs +495 -0
- package/dist/sig/accept.test.mjs +1 -1
- package/dist/sig/compound-proof-discovery.test.d.mts +2 -0
- package/dist/sig/compound-proof-discovery.test.mjs +331 -0
- package/dist/sig/compound-proof-producer.test.d.mts +2 -0
- package/dist/sig/compound-proof-producer.test.mjs +107 -0
- package/dist/sig/compound-proof-verification.test.d.mts +2 -0
- package/dist/sig/compound-proof-verification.test.mjs +833 -0
- package/dist/sig/compound-proof.test.d.mts +2 -0
- package/dist/sig/compound-proof.test.mjs +411 -0
- package/dist/sig/http.test.mjs +702 -31
- package/dist/sig/key-owner-evidence.test.d.mts +2 -0
- package/dist/sig/key-owner-evidence.test.mjs +100 -0
- package/dist/sig/key.test.mjs +220 -8
- package/dist/sig/ld.test.mjs +65 -7
- package/dist/sig/mitra-receive-interop.test.d.mts +2 -0
- package/dist/sig/mitra-receive-interop.test.mjs +260 -0
- package/dist/sig/mitra-send-interop.test.d.mts +2 -0
- package/dist/sig/mitra-send-interop.test.mjs +461 -0
- package/dist/sig/mod.cjs +18 -16
- package/dist/sig/mod.d.cts +4 -4
- package/dist/sig/mod.d.ts +4 -4
- package/dist/sig/mod.js +3 -3
- package/dist/sig/owner.test.mjs +108 -9
- package/dist/sig/portable-collection.test.d.mts +2 -0
- package/dist/sig/portable-collection.test.mjs +697 -0
- package/dist/sig/portable-interop.test.d.mts +2 -0
- package/dist/sig/portable-interop.test.mjs +156 -0
- package/dist/sig/portable-key-id.test.d.mts +2 -0
- package/dist/sig/portable-key-id.test.mjs +67 -0
- package/dist/sig/portable-key.test.d.mts +2 -0
- package/dist/sig/portable-key.test.mjs +1439 -0
- package/dist/sig/proof.test.mjs +1753 -16
- package/dist/sig/tootik-request-interop.test.d.mts +2 -0
- package/dist/sig/tootik-request-interop.test.mjs +190 -0
- package/dist/sig/tootik-send-interop.test.d.mts +2 -0
- package/dist/sig/tootik-send-interop.test.mjs +331 -0
- package/dist/sig/verification.test.d.mts +2 -0
- package/dist/sig/verification.test.mjs +310 -0
- package/dist/std__assert-DvMDRPes.mjs +8 -0
- package/dist/{testing-CO7ftO8A.mjs → tasks-IG3pHfvZ.mjs} +2 -2
- package/dist/{temporal-fsXO5IvD.mjs → temporal-D0KJI4NF.mjs} +3 -3
- package/dist/testing/context.test.d.mts +2 -0
- package/dist/testing/context.test.mjs +19 -0
- package/dist/testing/kv.test.d.mts +2 -0
- package/dist/testing/kv.test.mjs +101 -0
- package/dist/testing/mod.d.mts +1617 -48
- package/dist/testing/mod.mjs +6 -3
- package/dist/testing-t8xDeJZ5.mjs +8 -0
- package/dist/{transformers-DGwl-hD1.cjs → transformers-BalCrSsr.cjs} +0 -1
- package/dist/{types-DC1pNgJA.js → types-_S2_MeLF.js} +144 -8
- package/dist/{types-CPym83zc.cjs → types-poIao40f.cjs} +160 -7
- package/dist/utils/docloader.test.mjs +530 -8
- package/dist/utils/kv-cache.test.mjs +2 -2
- 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/dist/verification-C3954hQh.mjs +5 -0
- package/dist/verification-Dpfcw8ml.mjs +105 -0
- package/dist/vocab/cjs.test.mjs +1 -1
- package/package.json +8 -8
- package/dist/assert_rejects-EgstcQy5.mjs +0 -82
- package/dist/docloader-e1khNUKy.mjs +0 -49
- package/dist/http-CsmQ-BQ4.js +0 -2483
- package/dist/http-VyDTd4G3.d.cts +0 -553
- package/dist/http-hlIjmewS.cjs +0 -2694
- package/dist/http-lf8Hsd91.d.ts +0 -553
- package/dist/key-DfPsX8hv.mjs +0 -453
- package/dist/keycache-_DHqz5z_.mjs +0 -97
- package/dist/metrics-CoUJLQhZ.d.cts +0 -276
- package/dist/metrics-Dc4jR-IC.d.ts +0 -276
- package/dist/mod-B0hW12_O.d.cts +0 -307
- package/dist/mod-COIAjwRS.d.ts +0 -307
- package/dist/owner-Br9YYzn_.mjs +0 -120
- package/dist/proof-B4ngoaNQ.mjs +0 -316
- package/dist/proof-B6E0XUXJ.js +0 -1415
- package/dist/proof-NN4xCQGL.cjs +0 -1562
- package/dist/send-CxHKUNzv.mjs +0 -239
- package/dist/std__assert-B2NnY9bq.mjs +0 -79
- package/dist/temporal-polyfill-BCdTHexd.cjs +0 -3565
- /package/dist/{accept-8PiolDP7.mjs → accept-CPVqki78.mjs} +0 -0
- /package/dist/{activity-listener-BtTSxZWA.mjs → activity-listener-C-BSbKI7.mjs} +0 -0
- /package/dist/{client-CAM_bQXx.d.cts → client-D-XP_Ccb.d.cts} +0 -0
- /package/dist/{client-CSddvgWN.d.ts → client-D-XP_Ccb.d.ts} +0 -0
- /package/dist/{keys-Dbyd7bhl.mjs → keys-DIJNHW-Y.mjs} +0 -0
- /package/dist/{kv-CXBAok3k.mjs → kv-DCHkaJ9-.mjs} +0 -0
- /package/dist/{negotiation-RuwrPFqi.mjs → negotiation-CCocWT_N.mjs} +0 -0
- /package/dist/{retry-DgFqeS5k.mjs → retry-zX5xw5e0.mjs} +0 -0
- /package/dist/{chunk-D5IdezsZ.js → rolldown-runtime-BC-Eo5Mj.js} +0 -0
- /package/dist/{chunk-Czpeue4M.cjs → rolldown-runtime-Dmm3t92W.cjs} +0 -0
- /package/dist/{transformers-DXze6FUB.js → transformers-DAtXl_Kc.js} +0 -0
- /package/dist/{types-CrtdVuDP.mjs → types-DE_LV1JB.mjs} +0 -0
|
@@ -0,0 +1,495 @@
|
|
|
1
|
+
import "temporal-polyfill";
|
|
2
|
+
import "urlpattern-polyfill";
|
|
3
|
+
globalThis.addEventListener = () => {};
|
|
4
|
+
import { d as isPortableUri } from "./portable-key-id-Ca30-hOa.mjs";
|
|
5
|
+
import { n as version, t as name } from "./deno-oR5aJKpU.mjs";
|
|
6
|
+
import { n as getDurationMs, r as getFederationMetrics } from "./metrics-CYg9yxBL.mjs";
|
|
7
|
+
import { n as doubleKnock } from "./http-siciWkmx.mjs";
|
|
8
|
+
import { getLogger } from "@logtape/logtape";
|
|
9
|
+
import { SpanKind, SpanStatusCode, trace } from "@opentelemetry/api";
|
|
10
|
+
import { FetchError, UrlError, canonicalizePortableUri, getFe34Origin, isGatewayUrl, toCompatibleEf61Id, validatePublicUrl } from "@fedify/vocab-runtime";
|
|
11
|
+
import { getPortableGatewayCandidates } from "@fedify/vocab-runtime/internal/portable-dereference";
|
|
12
|
+
/**
|
|
13
|
+
* Resolves a portable inbox, i.e., an `ap:` or `ap+ef61:` URI, to
|
|
14
|
+
* the compatible identifiers of the inbox on the recipient's gateways.
|
|
15
|
+
*
|
|
16
|
+
* The gateways are taken from the recipient's `gateways`, or, if it has no
|
|
17
|
+
* valid gateway, from the `@gateway` location hints of the inbox URI. Both
|
|
18
|
+
* come from the recipient object, so they are as trustworthy as its inbox.
|
|
19
|
+
* Gateways whose origins are in `excludeBaseUris` are skipped.
|
|
20
|
+
* @param recipient The recipient that owns the inbox.
|
|
21
|
+
* @param inbox The portable inbox.
|
|
22
|
+
* @param excludeBaseUris The base URIs whose origins are not delivered to.
|
|
23
|
+
* @returns The delivery target, or `null` if the inbox is malformed, or no
|
|
24
|
+
* gateway is left to deliver through.
|
|
25
|
+
*/
|
|
26
|
+
function resolvePortableDeliveryTarget(recipient, inbox, excludeBaseUris) {
|
|
27
|
+
const logger = getLogger([
|
|
28
|
+
"fedify",
|
|
29
|
+
"federation",
|
|
30
|
+
"outbox"
|
|
31
|
+
]);
|
|
32
|
+
let portableInbox;
|
|
33
|
+
try {
|
|
34
|
+
portableInbox = canonicalizePortableUri(inbox.href);
|
|
35
|
+
} catch (error) {
|
|
36
|
+
if (!(error instanceof TypeError)) throw error;
|
|
37
|
+
logger.warn("Skipping the recipient {recipient}, as its portable inbox {inbox} is malformed.", {
|
|
38
|
+
recipient: recipient.id?.href,
|
|
39
|
+
inbox: inbox.href,
|
|
40
|
+
error
|
|
41
|
+
});
|
|
42
|
+
return null;
|
|
43
|
+
}
|
|
44
|
+
let gateways = (recipient.gateways ?? []).filter(isGatewayUrl);
|
|
45
|
+
if (gateways.length < 1) gateways = getPortableGatewayCandidates(inbox);
|
|
46
|
+
if (gateways.length < 1) {
|
|
47
|
+
logger.warn("Skipping the recipient {recipient}, as it has no gateway to deliver to its portable inbox {inbox} through.", {
|
|
48
|
+
recipient: recipient.id?.href,
|
|
49
|
+
inbox: portableInbox
|
|
50
|
+
});
|
|
51
|
+
return null;
|
|
52
|
+
}
|
|
53
|
+
const excludedOrigins = new Set(excludeBaseUris?.map((u) => u.origin));
|
|
54
|
+
const seen = /* @__PURE__ */ new Set();
|
|
55
|
+
const inboxes = [];
|
|
56
|
+
for (const gateway of gateways) {
|
|
57
|
+
if (inboxes.length >= 5) break;
|
|
58
|
+
if (seen.has(gateway.origin)) continue;
|
|
59
|
+
seen.add(gateway.origin);
|
|
60
|
+
if (excludedOrigins.has(gateway.origin)) continue;
|
|
61
|
+
try {
|
|
62
|
+
inboxes.push(toCompatibleEf61Id(inbox, gateway.origin));
|
|
63
|
+
} catch (error) {
|
|
64
|
+
if (!(error instanceof TypeError)) throw error;
|
|
65
|
+
logger.warn("Cannot deliver to the portable inbox {inbox} through the gateway {gateway}, as the inbox cannot be represented as its compatible identifier.", {
|
|
66
|
+
inbox: portableInbox,
|
|
67
|
+
gateway: gateway.origin,
|
|
68
|
+
error
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
if (inboxes.length < 1) {
|
|
73
|
+
logger.debug("Skipping the recipient {recipient}, as no gateway is left to deliver to its portable inbox {inbox} through.", {
|
|
74
|
+
recipient: recipient.id?.href,
|
|
75
|
+
inbox: portableInbox
|
|
76
|
+
});
|
|
77
|
+
return null;
|
|
78
|
+
}
|
|
79
|
+
return {
|
|
80
|
+
portableInbox,
|
|
81
|
+
inboxes
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Merges two ordered lists of compatible inbox URLs, keeping the first URL
|
|
86
|
+
* for each gateway origin and at most {@link MAX_DELIVERY_GATEWAYS} URLs.
|
|
87
|
+
* @param a The preferred list.
|
|
88
|
+
* @param b The other list.
|
|
89
|
+
* @returns The merged list.
|
|
90
|
+
*/
|
|
91
|
+
function mergeGatewayInboxes(a, b) {
|
|
92
|
+
const seen = /* @__PURE__ */ new Set();
|
|
93
|
+
const merged = [];
|
|
94
|
+
for (const inbox of [...a, ...b]) {
|
|
95
|
+
if (merged.length >= 5) break;
|
|
96
|
+
const { origin } = new URL(inbox);
|
|
97
|
+
if (seen.has(origin)) continue;
|
|
98
|
+
seen.add(origin);
|
|
99
|
+
merged.push(inbox);
|
|
100
|
+
}
|
|
101
|
+
return merged;
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Gets the part of a delivery's ordering key that tells its destination
|
|
105
|
+
* apart: the DID of a portable inbox, so that deliveries to it stay in order
|
|
106
|
+
* whichever gateway they go through, or the origin of any other inbox.
|
|
107
|
+
* @param inbox The inbox URL.
|
|
108
|
+
* @param portableInbox The canonical portable inbox, if the inbox is
|
|
109
|
+
* the compatible identifier of one.
|
|
110
|
+
* @returns The destination part of the ordering key.
|
|
111
|
+
*/
|
|
112
|
+
function getOrderingDestination(inbox, portableInbox) {
|
|
113
|
+
return portableInbox == null ? new URL(inbox).origin : getFe34Origin(portableInbox);
|
|
114
|
+
}
|
|
115
|
+
//#endregion
|
|
116
|
+
//#region src/federation/send.ts
|
|
117
|
+
/**
|
|
118
|
+
* Extracts the inbox URLs from recipients.
|
|
119
|
+
*
|
|
120
|
+
* An [FEP-ef61] portable inbox, i.e., an `ap:` or `ap+ef61:` URI, is resolved
|
|
121
|
+
* to its compatible identifiers on the recipient's gateways, or, if the
|
|
122
|
+
* recipient has no valid gateway, on the gateways in the `@gateway` location
|
|
123
|
+
* hints of the inbox URI. The inbox is keyed by the compatible identifier on
|
|
124
|
+
* the first gateway, and the others are listed in
|
|
125
|
+
* {@link ExtractedInbox.gatewayInboxes}. A recipient whose portable inbox
|
|
126
|
+
* has no gateway to deliver through is skipped. The shared inbox of
|
|
127
|
+
* a recipient with a portable inbox is never preferred, since only a portable
|
|
128
|
+
* inbox is synchronized between the gateways.
|
|
129
|
+
*
|
|
130
|
+
* [FEP-ef61]: https://w3id.org/fep/ef61
|
|
131
|
+
* @param parameters The parameters to extract the inboxes.
|
|
132
|
+
* See also {@link ExtractInboxesParameters}.
|
|
133
|
+
* @returns The inboxes as a map of inbox URL to actor URIs.
|
|
134
|
+
*/
|
|
135
|
+
function extractInboxes({ recipients, preferSharedInbox, excludeBaseUris }) {
|
|
136
|
+
const inboxes = {};
|
|
137
|
+
const portableKeys = /* @__PURE__ */ new Map();
|
|
138
|
+
const compatibleKeys = /* @__PURE__ */ new Map();
|
|
139
|
+
for (const recipient of recipients) {
|
|
140
|
+
if (recipient.id == null) continue;
|
|
141
|
+
let inbox;
|
|
142
|
+
let sharedInbox = false;
|
|
143
|
+
if (preferSharedInbox && recipient.endpoints?.sharedInbox != null && (recipient.inboxId == null || !isPortableUri(recipient.inboxId))) {
|
|
144
|
+
inbox = recipient.endpoints.sharedInbox;
|
|
145
|
+
sharedInbox = true;
|
|
146
|
+
} else inbox = recipient.inboxId;
|
|
147
|
+
if (inbox == null) continue;
|
|
148
|
+
if (isPortableUri(inbox)) {
|
|
149
|
+
const target = resolvePortableDeliveryTarget(recipient, inbox, excludeBaseUris);
|
|
150
|
+
if (target == null) continue;
|
|
151
|
+
const gatewayInboxes = target.inboxes.map((u) => u.href);
|
|
152
|
+
const existingKeys = /* @__PURE__ */ new Set();
|
|
153
|
+
const portableKey = portableKeys.get(target.portableInbox);
|
|
154
|
+
if (portableKey != null) existingKeys.add(portableKey);
|
|
155
|
+
for (const i of gatewayInboxes) {
|
|
156
|
+
const k = compatibleKeys.get(i) ?? i;
|
|
157
|
+
if (Object.hasOwn(inboxes, k)) existingKeys.add(k);
|
|
158
|
+
}
|
|
159
|
+
const [key = gatewayInboxes[0], ...otherKeys] = existingKeys;
|
|
160
|
+
portableKeys.set(target.portableInbox, key);
|
|
161
|
+
const entry = inboxes[key] ??= {
|
|
162
|
+
actorIds: /* @__PURE__ */ new Set(),
|
|
163
|
+
sharedInbox
|
|
164
|
+
};
|
|
165
|
+
for (const k of otherKeys) {
|
|
166
|
+
for (const actorId of inboxes[k].actorIds) entry.actorIds.add(actorId);
|
|
167
|
+
delete inboxes[k];
|
|
168
|
+
}
|
|
169
|
+
entry.sharedInbox = false;
|
|
170
|
+
entry.portableInbox = target.portableInbox;
|
|
171
|
+
entry.gatewayInboxes = mergeGatewayInboxes(entry.gatewayInboxes ?? [key], gatewayInboxes);
|
|
172
|
+
for (const i of entry.gatewayInboxes) compatibleKeys.set(i, key);
|
|
173
|
+
entry.actorIds.add(recipient.id.href);
|
|
174
|
+
continue;
|
|
175
|
+
}
|
|
176
|
+
if (excludeBaseUris != null && excludeBaseUris.some((u) => u.origin === inbox?.origin)) continue;
|
|
177
|
+
const key = compatibleKeys.get(inbox.href) ?? inbox.href;
|
|
178
|
+
inboxes[key] ??= {
|
|
179
|
+
actorIds: /* @__PURE__ */ new Set(),
|
|
180
|
+
sharedInbox
|
|
181
|
+
};
|
|
182
|
+
inboxes[key].actorIds.add(recipient.id.href);
|
|
183
|
+
}
|
|
184
|
+
return inboxes;
|
|
185
|
+
}
|
|
186
|
+
/**
|
|
187
|
+
* Sends an {@link Activity} to an inbox.
|
|
188
|
+
*
|
|
189
|
+
* @param parameters The parameters for sending the activity.
|
|
190
|
+
* See also {@link SendActivityParameters}.
|
|
191
|
+
* @throws {Error} If the activity fails to send.
|
|
192
|
+
*/
|
|
193
|
+
function sendActivity(options) {
|
|
194
|
+
const tracerProvider = options.tracerProvider ?? trace.getTracerProvider();
|
|
195
|
+
return tracerProvider.getTracer(name, version).startActiveSpan("activitypub.send_activity", {
|
|
196
|
+
kind: SpanKind.CLIENT,
|
|
197
|
+
attributes: { "activitypub.shared_inbox": options.sharedInbox ?? false }
|
|
198
|
+
}, async (span) => {
|
|
199
|
+
if (options.activityId != null) span.setAttribute("activitypub.activity.id", options.activityId);
|
|
200
|
+
if (options.activityType != null) span.setAttribute("activitypub.activity.type", options.activityType);
|
|
201
|
+
try {
|
|
202
|
+
await sendActivityInternal({
|
|
203
|
+
...options,
|
|
204
|
+
tracerProvider
|
|
205
|
+
}, span);
|
|
206
|
+
} catch (e) {
|
|
207
|
+
span.setStatus({
|
|
208
|
+
code: SpanStatusCode.ERROR,
|
|
209
|
+
message: String(e)
|
|
210
|
+
});
|
|
211
|
+
throw e;
|
|
212
|
+
} finally {
|
|
213
|
+
span.end();
|
|
214
|
+
}
|
|
215
|
+
});
|
|
216
|
+
}
|
|
217
|
+
/**
|
|
218
|
+
* Sends an activity to the first of the given inboxes that accepts it, i.e.,
|
|
219
|
+
* the compatible identifiers of a portable inbox on its gateways, trying them
|
|
220
|
+
* one after another.
|
|
221
|
+
* @param parameters The parameters, except for the inbox.
|
|
222
|
+
* @param inboxes The inboxes to try, in order. Must not be empty.
|
|
223
|
+
* @throws {Error} The error of the last inbox if no inbox accepts it.
|
|
224
|
+
*/
|
|
225
|
+
async function sendActivityThroughGateways(parameters, inboxes) {
|
|
226
|
+
const logger = getLogger([
|
|
227
|
+
"fedify",
|
|
228
|
+
"federation",
|
|
229
|
+
"outbox"
|
|
230
|
+
]);
|
|
231
|
+
let lastError;
|
|
232
|
+
for (const [i, inbox] of inboxes.entries()) try {
|
|
233
|
+
await sendActivity({
|
|
234
|
+
...parameters,
|
|
235
|
+
inbox
|
|
236
|
+
});
|
|
237
|
+
return;
|
|
238
|
+
} catch (error) {
|
|
239
|
+
lastError = error;
|
|
240
|
+
if (i < inboxes.length - 1) logger.warn("Failed to send activity {activityId} to {inbox}; trying the next gateway {next}:\n{error}", {
|
|
241
|
+
activityId: parameters.activityId,
|
|
242
|
+
inbox: inbox.href,
|
|
243
|
+
next: inboxes[i + 1].href,
|
|
244
|
+
error
|
|
245
|
+
});
|
|
246
|
+
}
|
|
247
|
+
throw lastError;
|
|
248
|
+
}
|
|
249
|
+
const MAX_ERROR_RESPONSE_BODY_BYTES = 1024;
|
|
250
|
+
function getActivityActorId(activity) {
|
|
251
|
+
if (!isRecord(activity)) return void 0;
|
|
252
|
+
return getIdValue(activity.actor);
|
|
253
|
+
}
|
|
254
|
+
function getIdValue(value) {
|
|
255
|
+
if (typeof value === "string" && value !== "") return value;
|
|
256
|
+
if (value instanceof URL) return value.href;
|
|
257
|
+
if (Array.isArray(value)) {
|
|
258
|
+
for (const item of value) {
|
|
259
|
+
const id = getIdValue(item);
|
|
260
|
+
if (id != null) return id;
|
|
261
|
+
}
|
|
262
|
+
return;
|
|
263
|
+
}
|
|
264
|
+
if (isRecord(value)) return getIdValue(value.id);
|
|
265
|
+
}
|
|
266
|
+
function isRecord(value) {
|
|
267
|
+
return typeof value === "object" && value != null;
|
|
268
|
+
}
|
|
269
|
+
async function readLimitedResponseBody(response, maxBytes) {
|
|
270
|
+
if (response.body == null) return "";
|
|
271
|
+
const reader = response.body.getReader();
|
|
272
|
+
const decoder = new TextDecoder();
|
|
273
|
+
const chunks = [];
|
|
274
|
+
let totalBytes = 0;
|
|
275
|
+
let truncated = false;
|
|
276
|
+
try {
|
|
277
|
+
while (true) {
|
|
278
|
+
const { done, value } = await reader.read();
|
|
279
|
+
if (done) break;
|
|
280
|
+
if (totalBytes + value.length > maxBytes) {
|
|
281
|
+
const remaining = maxBytes - totalBytes;
|
|
282
|
+
if (remaining > 0) chunks.push(decoder.decode(value.slice(0, remaining), { stream: true }));
|
|
283
|
+
truncated = true;
|
|
284
|
+
break;
|
|
285
|
+
}
|
|
286
|
+
chunks.push(decoder.decode(value, { stream: true }));
|
|
287
|
+
totalBytes += value.length;
|
|
288
|
+
}
|
|
289
|
+
} finally {
|
|
290
|
+
reader.releaseLock();
|
|
291
|
+
}
|
|
292
|
+
let result = chunks.join("");
|
|
293
|
+
if (truncated) result += "… (truncated)";
|
|
294
|
+
return result;
|
|
295
|
+
}
|
|
296
|
+
async function sendActivityInternal({ activity, activityId, activityType, keys, inbox, allowPrivateAddress, headers, specDeterminer, meterProvider, tracerProvider }, span) {
|
|
297
|
+
const logger = getLogger([
|
|
298
|
+
"fedify",
|
|
299
|
+
"federation",
|
|
300
|
+
"outbox"
|
|
301
|
+
]);
|
|
302
|
+
const federationMetrics = getFederationMetrics(meterProvider);
|
|
303
|
+
const started = performance.now();
|
|
304
|
+
let deliverySuccess = false;
|
|
305
|
+
async function validateUrl(url) {
|
|
306
|
+
if (!allowPrivateAddress) try {
|
|
307
|
+
await validatePublicUrl(url);
|
|
308
|
+
} catch (error) {
|
|
309
|
+
if (error instanceof UrlError && error.reason === "dns") {
|
|
310
|
+
logger.error("DNS resolution failed for URL: {url}", {
|
|
311
|
+
url,
|
|
312
|
+
error
|
|
313
|
+
});
|
|
314
|
+
const failure = new FetchError(url, error.message);
|
|
315
|
+
failure.cause = error;
|
|
316
|
+
throw failure;
|
|
317
|
+
}
|
|
318
|
+
if (error instanceof UrlError) logger.error("Disallowed private URL: {url}", {
|
|
319
|
+
url,
|
|
320
|
+
error
|
|
321
|
+
});
|
|
322
|
+
throw error;
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
try {
|
|
326
|
+
await validateUrl(inbox.href);
|
|
327
|
+
} catch (error) {
|
|
328
|
+
if (error instanceof FetchError) federationMetrics.recordDelivery(inbox, getDurationMs(started), false, activityType);
|
|
329
|
+
throw error;
|
|
330
|
+
}
|
|
331
|
+
headers = new Headers(headers);
|
|
332
|
+
headers.set("Content-Type", "application/activity+json");
|
|
333
|
+
const request = new Request(inbox, {
|
|
334
|
+
method: "POST",
|
|
335
|
+
headers,
|
|
336
|
+
body: JSON.stringify(activity),
|
|
337
|
+
redirect: "manual"
|
|
338
|
+
});
|
|
339
|
+
let rsaKey = null;
|
|
340
|
+
for (const key of keys) if (key.privateKey.algorithm.name === "RSASSA-PKCS1-v1_5") {
|
|
341
|
+
rsaKey = key;
|
|
342
|
+
break;
|
|
343
|
+
}
|
|
344
|
+
if (rsaKey == null && keys.length < 1) logger.debug("Sending the activity {activityId} to {inbox} without a signature.", {
|
|
345
|
+
activityId,
|
|
346
|
+
inbox: inbox.href
|
|
347
|
+
});
|
|
348
|
+
else 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.", {
|
|
349
|
+
inbox: inbox.href,
|
|
350
|
+
keys: keys.map((pair) => ({
|
|
351
|
+
keyId: pair.keyId.href,
|
|
352
|
+
privateKey: pair.privateKey
|
|
353
|
+
}))
|
|
354
|
+
});
|
|
355
|
+
let response;
|
|
356
|
+
try {
|
|
357
|
+
response = rsaKey == null ? await fetchWithValidatedRedirects(request, validateUrl) : await doubleKnock(request, rsaKey, {
|
|
358
|
+
tracerProvider,
|
|
359
|
+
specDeterminer,
|
|
360
|
+
validateRedirect: validateUrl
|
|
361
|
+
});
|
|
362
|
+
} catch (error) {
|
|
363
|
+
const failure = error instanceof FetchError || error instanceof UrlError ? error : createFetchError(inbox.href, error);
|
|
364
|
+
logger.error("Failed to send activity {activityId} to {inbox}:\n{error}", {
|
|
365
|
+
activityId,
|
|
366
|
+
inbox: inbox.href,
|
|
367
|
+
error: failure
|
|
368
|
+
});
|
|
369
|
+
federationMetrics.recordDelivery(inbox, getDurationMs(started), false, activityType);
|
|
370
|
+
throw failure;
|
|
371
|
+
}
|
|
372
|
+
try {
|
|
373
|
+
if (!response.ok) {
|
|
374
|
+
let error;
|
|
375
|
+
try {
|
|
376
|
+
error = await readLimitedResponseBody(response, MAX_ERROR_RESPONSE_BODY_BYTES);
|
|
377
|
+
} catch (_) {
|
|
378
|
+
error = "";
|
|
379
|
+
}
|
|
380
|
+
logger.error("Failed to send activity {activityId} to {inbox} ({status} {statusText}):\n{error}", {
|
|
381
|
+
activityId,
|
|
382
|
+
inbox: inbox.href,
|
|
383
|
+
status: response.status,
|
|
384
|
+
statusText: response.statusText,
|
|
385
|
+
error
|
|
386
|
+
});
|
|
387
|
+
throw new SendActivityError(inbox, response.status, `Failed to send activity ${activityId} to ${inbox.href} (${response.status} ${response.statusText}):\n${error}`, error, response.headers);
|
|
388
|
+
}
|
|
389
|
+
deliverySuccess = true;
|
|
390
|
+
const eventAttributes = {
|
|
391
|
+
"activitypub.inbox.url": inbox.href,
|
|
392
|
+
"activitypub.activity.id": activityId ?? ""
|
|
393
|
+
};
|
|
394
|
+
if (activityType != null) eventAttributes["activitypub.activity.type"] = activityType;
|
|
395
|
+
const actorId = getActivityActorId(activity);
|
|
396
|
+
if (actorId != null) eventAttributes["activitypub.actor.id"] = actorId;
|
|
397
|
+
span.addEvent("activitypub.activity.sent", eventAttributes);
|
|
398
|
+
} finally {
|
|
399
|
+
federationMetrics.recordDelivery(inbox, getDurationMs(started), deliverySuccess, activityType);
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
function createFetchError(url, cause) {
|
|
403
|
+
const error = new FetchError(url, cause instanceof Error ? cause.message : String(cause));
|
|
404
|
+
error.cause = cause;
|
|
405
|
+
return error;
|
|
406
|
+
}
|
|
407
|
+
async function fetchWithValidatedRedirects(request, validateUrl) {
|
|
408
|
+
let body = await request.clone().text();
|
|
409
|
+
for (let redirects = 0;; redirects++) {
|
|
410
|
+
const response = await fetch(request, { redirect: "manual" });
|
|
411
|
+
const location = response.headers.get("Location");
|
|
412
|
+
if (![
|
|
413
|
+
301,
|
|
414
|
+
302,
|
|
415
|
+
303,
|
|
416
|
+
307,
|
|
417
|
+
308
|
|
418
|
+
].includes(response.status) || location == null) return response;
|
|
419
|
+
await response.body?.cancel();
|
|
420
|
+
if (redirects >= 20) throw new TypeError("Too many redirects");
|
|
421
|
+
const url = new URL(location, request.url);
|
|
422
|
+
await validateUrl(url.href);
|
|
423
|
+
const headers = new Headers(request.headers);
|
|
424
|
+
if (url.origin !== new URL(request.url).origin) {
|
|
425
|
+
headers.delete("Authorization");
|
|
426
|
+
headers.delete("Proxy-Authorization");
|
|
427
|
+
headers.delete("Cookie");
|
|
428
|
+
headers.delete("Host");
|
|
429
|
+
}
|
|
430
|
+
let method = request.method;
|
|
431
|
+
if ((response.status === 301 || response.status === 302) && method === "POST" || response.status === 303 && method !== "GET" && method !== "HEAD") {
|
|
432
|
+
method = "GET";
|
|
433
|
+
body = void 0;
|
|
434
|
+
for (const name of [
|
|
435
|
+
"Content-Encoding",
|
|
436
|
+
"Content-Language",
|
|
437
|
+
"Content-Length",
|
|
438
|
+
"Content-Location",
|
|
439
|
+
"Content-Type"
|
|
440
|
+
]) headers.delete(name);
|
|
441
|
+
}
|
|
442
|
+
request = new Request(url, {
|
|
443
|
+
method,
|
|
444
|
+
headers,
|
|
445
|
+
body,
|
|
446
|
+
redirect: "manual"
|
|
447
|
+
});
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
/**
|
|
451
|
+
* An error that is thrown when an activity fails to send to a remote inbox.
|
|
452
|
+
* It contains structured information about the failure, including the HTTP
|
|
453
|
+
* status code, the inbox URL, and the response body.
|
|
454
|
+
* @since 2.0.0
|
|
455
|
+
*/
|
|
456
|
+
var SendActivityError = class extends Error {
|
|
457
|
+
/**
|
|
458
|
+
* The inbox URL that the activity was being sent to.
|
|
459
|
+
*/
|
|
460
|
+
inbox;
|
|
461
|
+
/**
|
|
462
|
+
* The HTTP status code returned by the inbox.
|
|
463
|
+
*/
|
|
464
|
+
statusCode;
|
|
465
|
+
/**
|
|
466
|
+
* The response body from the inbox, if any. Note that this may be
|
|
467
|
+
* truncated to a maximum of 1 KiB to prevent excessive memory consumption
|
|
468
|
+
* when remote servers return large error pages (e.g., Cloudflare error pages).
|
|
469
|
+
* If truncated, the string will end with `"… (truncated)"`.
|
|
470
|
+
*/
|
|
471
|
+
responseBody;
|
|
472
|
+
/**
|
|
473
|
+
* The response headers from the inbox.
|
|
474
|
+
* @since 2.3.0
|
|
475
|
+
*/
|
|
476
|
+
responseHeaders;
|
|
477
|
+
/**
|
|
478
|
+
* Creates a new {@link SendActivityError}.
|
|
479
|
+
* @param inbox The inbox URL.
|
|
480
|
+
* @param statusCode The HTTP status code.
|
|
481
|
+
* @param message The error message.
|
|
482
|
+
* @param responseBody The response body.
|
|
483
|
+
* @param responseHeaders The response headers.
|
|
484
|
+
*/
|
|
485
|
+
constructor(inbox, statusCode, message, responseBody, responseHeaders) {
|
|
486
|
+
super(message);
|
|
487
|
+
this.name = "SendActivityError";
|
|
488
|
+
this.inbox = inbox;
|
|
489
|
+
this.statusCode = statusCode;
|
|
490
|
+
this.responseBody = responseBody;
|
|
491
|
+
this.responseHeaders = new Headers(responseHeaders);
|
|
492
|
+
}
|
|
493
|
+
};
|
|
494
|
+
//#endregion
|
|
495
|
+
export { getOrderingDestination as a, sendActivityThroughGateways as i, extractInboxes as n, sendActivity as r, SendActivityError as t };
|
package/dist/sig/accept.test.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import "temporal-polyfill";
|
|
2
2
|
import "urlpattern-polyfill";
|
|
3
3
|
globalThis.addEventListener = () => {};
|
|
4
|
-
import { i as validateAcceptSignature, n as fulfillAcceptSignature, r as parseAcceptSignature, t as formatAcceptSignature } from "../accept-
|
|
4
|
+
import { i as validateAcceptSignature, n as fulfillAcceptSignature, r as parseAcceptSignature, t as formatAcceptSignature } from "../accept-CPVqki78.mjs";
|
|
5
5
|
import { test } from "@fedify/fixture";
|
|
6
6
|
import { deepStrictEqual, strictEqual } from "node:assert/strict";
|
|
7
7
|
//#region src/sig/accept.test.ts
|