@fedify/fedify 2.3.1 → 2.3.2
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 +11 -8
- package/dist/{builder-hivpmhlD.mjs → builder-Dx4EI2YS.mjs} +4 -2
- package/dist/{circuit-breaker-CSWsyoef.mjs → circuit-breaker-DLS_n1x5.mjs} +190 -7
- package/dist/circuit-breaker-test-utils-BVnv3T76.mjs +14 -0
- package/dist/{client-ByXmQhYD.mjs → client-D1KuWxjH.mjs} +63 -5
- 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-BBVLF7lx.d.cts → context-Bjswu3eC.d.cts} +18 -1
- package/dist/{context-BU6jSQdo.d.ts → context-COxc_O-c.d.ts} +18 -1
- package/dist/{deno-C8RG0HM3.mjs → deno-B8lcQ4Ms.mjs} +1 -1
- package/dist/{docloader-YZA0m_3O.mjs → docloader-BMjxpyJD.mjs} +2 -2
- package/dist/federation/builder.test.mjs +13 -1
- package/dist/federation/circuit-breaker.test.mjs +622 -3
- package/dist/federation/handler.test.mjs +3 -3
- package/dist/federation/idempotency.test.mjs +2 -2
- package/dist/federation/metrics.test.mjs +1 -1
- package/dist/federation/middleware.test.mjs +16 -8
- 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/send.test.mjs +3 -3
- package/dist/federation/temporal.test.mjs +1 -1
- package/dist/federation/webfinger.test.mjs +69 -7
- package/dist/{http-BajqV0pH.cjs → http-CpdQD2ke.cjs} +1 -1
- package/dist/{http-DPOMa1DD.js → http-DVZxcZEr.js} +1 -1
- package/dist/{http-PCt0862M.mjs → http-DlSQMJsb.mjs} +3 -3
- package/dist/{key-BKYwmbur.mjs → key-BqT9YrM3.mjs} +2 -2
- package/dist/{kv-cache-IjRceERu.js → kv-cache-B52kTPj4.js} +1 -1
- package/dist/{kv-cache-CVG3m2_-.cjs → kv-cache-B8gm4cVB.cjs} +1 -1
- package/dist/{kv-cache-Ceg0c1wh.mjs → kv-cache-BD6BgCTF.mjs} +1 -1
- package/dist/{ld-Dcw2skfn.mjs → ld-hwRDfZqq.mjs} +3 -3
- package/dist/{metrics-Pue1x4ta.mjs → metrics-DxJzdAgZ.mjs} +1 -1
- package/dist/{middleware-By8lqc18.js → middleware-B2rDMvhv.js} +231 -29
- package/dist/{middleware-WU_5fbGS.mjs → middleware-CubmqVku.mjs} +49 -32
- package/dist/{middleware-BiXEAbn2.cjs → middleware-D2F1jSer.cjs} +231 -29
- package/dist/{middleware-CyxqoeW9.mjs → middleware-DxCKBFFT.mjs} +1 -1
- package/dist/{mod-C0F6kvgS.d.cts → mod-0bIqGrKQ.d.cts} +1 -1
- package/dist/{mod-vPYVoa5n.d.ts → mod-3H9Gym5G.d.ts} +1 -1
- package/dist/mod.cjs +5 -5
- package/dist/mod.d.cts +2 -2
- package/dist/mod.d.ts +2 -2
- package/dist/mod.js +5 -5
- package/dist/nodeinfo/client.test.mjs +61 -1
- package/dist/nodeinfo/handler.test.mjs +1 -1
- package/dist/nodeinfo/mod.cjs +1 -1
- package/dist/nodeinfo/mod.js +1 -1
- package/dist/{owner-pVJ0Twi7.mjs → owner-Btzxz2vj.mjs} +2 -2
- package/dist/{proof-HzavUYxN.cjs → proof-BdnNZ9OM.cjs} +1 -1
- package/dist/{proof-DA8Kbchf.js → proof-C_kcg7vF.js} +1 -1
- package/dist/{proof-pXYODmNV.mjs → proof-D2Z0Wa9X.mjs} +3 -3
- package/dist/{send-BfAp60BJ.mjs → send-BljvjEOa.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 +2 -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-HZHBbbk4.mjs → temporal-Dg8Hb3lz.mjs} +1 -1
- package/dist/{types-CAY3OdLq.js → types-DTONwNqL.js} +63 -5
- package/dist/{types-KC4QAoxe.cjs → types-m8u9L86X.cjs} +62 -4
- 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/skills/fedify/SKILL.md +462 -0
|
@@ -2,10 +2,10 @@ import { Temporal } from "@js-temporal/polyfill";
|
|
|
2
2
|
import { URLPattern } from "urlpattern-polyfill";
|
|
3
3
|
import { t as __exportAll } from "./chunk-CRNNMoPX.js";
|
|
4
4
|
import { r as getDefaultActivityTransformers } from "./transformers-BGMIq1cs.js";
|
|
5
|
-
import { A as formatAcceptSignature, D as recordOutboxEnqueue, E as recordOutboxActivity, F as version, O as recordWebFingerHandle, P as name, S as recordCollectionTotalItems, T as recordInboxActivity, a as verifyRequestDetailed, b as recordCollectionPageItems, d as validateCryptoKey, f as getDurationMs, g as isAbortError, h as instrumentDocumentLoader, i as verifyRequest, k as registerQueueDepthGauge, m as getRemoteHost, n as parseRfc9421SignatureInput, o as exportJwk, p as getFederationMetrics, t as doubleKnock, u as importJwk, v as recordCircuitBreakerStateChange, w as recordFanoutRecipients, x as recordCollectionRequest, y as recordCollectionDispatchDuration } from "./http-
|
|
6
|
-
import { _ as hasSignatureLike, b as signJsonLd, c as getKeyOwner, f as compactJsonLd, g as hasSignature, h as getNormalizationContextLoader, i as verifyObject, l as InvalidContextReferenceError, m as detachSignature, n as hasProofLike, o as normalizeOutgoingActivityJsonLd, r as signObject, s as doesActorOwnKey, u as assertSafeJsonLd, v as isClearlyMalformedContextReference, w as wrapContextLoaderForJsonLd, x as verifyCompactJsonLd, y as isInvalidUrlTypeError } from "./proof-
|
|
7
|
-
import { n as getNodeInfo, t as nodeInfoToJson } from "./types-
|
|
8
|
-
import { n as getAuthenticatedDocumentLoader, t as kvCache } from "./kv-cache-
|
|
5
|
+
import { A as formatAcceptSignature, D as recordOutboxEnqueue, E as recordOutboxActivity, F as version, O as recordWebFingerHandle, P as name, S as recordCollectionTotalItems, T as recordInboxActivity, a as verifyRequestDetailed, b as recordCollectionPageItems, d as validateCryptoKey, f as getDurationMs, g as isAbortError, h as instrumentDocumentLoader, i as verifyRequest, k as registerQueueDepthGauge, m as getRemoteHost, n as parseRfc9421SignatureInput, o as exportJwk, p as getFederationMetrics, t as doubleKnock, u as importJwk, v as recordCircuitBreakerStateChange, w as recordFanoutRecipients, x as recordCollectionRequest, y as recordCollectionDispatchDuration } from "./http-DVZxcZEr.js";
|
|
6
|
+
import { _ as hasSignatureLike, b as signJsonLd, c as getKeyOwner, f as compactJsonLd, g as hasSignature, h as getNormalizationContextLoader, i as verifyObject, l as InvalidContextReferenceError, m as detachSignature, n as hasProofLike, o as normalizeOutgoingActivityJsonLd, r as signObject, s as doesActorOwnKey, u as assertSafeJsonLd, v as isClearlyMalformedContextReference, w as wrapContextLoaderForJsonLd, x as verifyCompactJsonLd, y as isInvalidUrlTypeError } from "./proof-C_kcg7vF.js";
|
|
7
|
+
import { n as getNodeInfo, t as nodeInfoToJson } from "./types-DTONwNqL.js";
|
|
8
|
+
import { n as getAuthenticatedDocumentLoader, t as kvCache } from "./kv-cache-B52kTPj4.js";
|
|
9
9
|
import { getLogger, withContext } from "@logtape/logtape";
|
|
10
10
|
import { Router, RouterError, assertPath } from "@fedify/uri-template";
|
|
11
11
|
import { Activity, Collection, CollectionPage, CryptographicKey, Link, Multikey, Object as Object$1, OrderedCollection, OrderedCollectionPage, Tombstone, getTypeId, lookupObject, traverseCollection } from "@fedify/vocab";
|
|
@@ -137,6 +137,8 @@ var FederationBuilderImpl = class {
|
|
|
137
137
|
f.webFingerLinksDispatcher = this.webFingerLinksDispatcher;
|
|
138
138
|
f.objectCallbacks = { ...this.objectCallbacks };
|
|
139
139
|
f.objectTypeIds = { ...this.objectTypeIds };
|
|
140
|
+
f.collectionCallbacks = { ...this.collectionCallbacks };
|
|
141
|
+
f.collectionTypeIds = { ...this.collectionTypeIds };
|
|
140
142
|
f.inboxPath = this.inboxPath;
|
|
141
143
|
f.outboxPath = this.outboxPath;
|
|
142
144
|
f.inboxCallbacks = this.inboxCallbacks == null ? void 0 : { ...this.inboxCallbacks };
|
|
@@ -680,6 +682,13 @@ function createFederationBuilder() {
|
|
|
680
682
|
//#endregion
|
|
681
683
|
//#region src/federation/circuit-breaker.ts
|
|
682
684
|
const MAX_CUSTOM_FAILURE_HISTORY = 100;
|
|
685
|
+
const LEGACY_SWEEP_MARKER = ["__fedify_meta", "circuit_breaker_state_ttl_sweep_v1"];
|
|
686
|
+
const LEGACY_SWEEP_DELETING_MARKER = { __fedifyDeletingCircuitBreakerLegacyState: true };
|
|
687
|
+
const CIRCUIT_BREAKER_STATE_VERSION = 1;
|
|
688
|
+
const LEGACY_SWEEP_LOCK_TTL = Temporal.Duration.from({ minutes: 5 });
|
|
689
|
+
const LEGACY_SWEEP_RETRY_WINDOW = Temporal.Duration.from({ hours: 168 });
|
|
690
|
+
const LEGACY_SWEEP_WAIT_INTERVAL = 10;
|
|
691
|
+
const LEGACY_SWEEP_CAS_ATTEMPTS = 3;
|
|
683
692
|
/**
|
|
684
693
|
* Tracks reachability state for remote outbox delivery hosts.
|
|
685
694
|
* @since 2.3.0
|
|
@@ -689,6 +698,8 @@ var CircuitBreaker = class {
|
|
|
689
698
|
#prefix;
|
|
690
699
|
#options;
|
|
691
700
|
#now;
|
|
701
|
+
#legacySweep;
|
|
702
|
+
#legacySweepDone;
|
|
692
703
|
#stateChangeObserver;
|
|
693
704
|
constructor(options) {
|
|
694
705
|
this.#kv = options.kv;
|
|
@@ -705,6 +716,7 @@ var CircuitBreaker = class {
|
|
|
705
716
|
return now.until(this.#capHeldRetryAt(now, heldSince, now.add(delay)));
|
|
706
717
|
}
|
|
707
718
|
async beforeSend(remoteHost, message) {
|
|
719
|
+
this.#sweepLegacyStates();
|
|
708
720
|
const heldSince = parseHeldSince(message.circuitHeldSince);
|
|
709
721
|
const now = this.#now();
|
|
710
722
|
if (heldSince != null && Temporal.Instant.compare(heldSince.add(this.#options.heldActivityTtl), now) <= 0) return {
|
|
@@ -786,6 +798,7 @@ var CircuitBreaker = class {
|
|
|
786
798
|
throw new Error(`Failed to update circuit breaker state for ${remoteHost}`);
|
|
787
799
|
}
|
|
788
800
|
async recordSuccess(remoteHost) {
|
|
801
|
+
this.#sweepLegacyStates();
|
|
789
802
|
for (let attempt = 0; attempt < 10; attempt++) {
|
|
790
803
|
const oldState = await this.#get(remoteHost);
|
|
791
804
|
if (oldState == null) return void 0;
|
|
@@ -806,6 +819,7 @@ var CircuitBreaker = class {
|
|
|
806
819
|
return await this.recordSuccess(remoteHost);
|
|
807
820
|
}
|
|
808
821
|
async recordFailure(remoteHost) {
|
|
822
|
+
this.#sweepLegacyStates();
|
|
809
823
|
const now = this.#now();
|
|
810
824
|
for (let attempt = 0; attempt < 10; attempt++) {
|
|
811
825
|
const oldState = await this.#get(remoteHost);
|
|
@@ -853,26 +867,122 @@ var CircuitBreaker = class {
|
|
|
853
867
|
}
|
|
854
868
|
}
|
|
855
869
|
async getState(remoteHost) {
|
|
856
|
-
|
|
870
|
+
this.#sweepLegacyStates();
|
|
871
|
+
return stripStoredCircuitBreakerState(await this.#get(remoteHost));
|
|
872
|
+
}
|
|
873
|
+
/**
|
|
874
|
+
* The currently running background legacy sweep, if any.
|
|
875
|
+
* @internal
|
|
876
|
+
*/
|
|
877
|
+
get pendingSweep() {
|
|
878
|
+
return this.#legacySweep;
|
|
857
879
|
}
|
|
858
880
|
#key(remoteHost) {
|
|
859
881
|
return [...this.#prefix, remoteHost];
|
|
860
882
|
}
|
|
883
|
+
#legacySweepMarkerKey() {
|
|
884
|
+
return [...this.#prefix, ...LEGACY_SWEEP_MARKER];
|
|
885
|
+
}
|
|
886
|
+
#sweepLegacyStates() {
|
|
887
|
+
if (this.#kv.cas == null) return;
|
|
888
|
+
if (this.#options.stateTtl == null) return;
|
|
889
|
+
if (this.#legacySweep != null) return;
|
|
890
|
+
if (this.#isLegacySweepDone()) return;
|
|
891
|
+
this.#legacySweep = this.#sweepLegacyStatesImpl().catch((error) => {
|
|
892
|
+
getLogger([
|
|
893
|
+
"fedify",
|
|
894
|
+
"federation",
|
|
895
|
+
"circuit"
|
|
896
|
+
]).warn("Failed to sweep legacy circuit breaker state:\n{error}", { error });
|
|
897
|
+
}).finally(() => {
|
|
898
|
+
this.#legacySweep = void 0;
|
|
899
|
+
});
|
|
900
|
+
}
|
|
901
|
+
async #sweepLegacyStatesImpl() {
|
|
902
|
+
const markerKey = this.#legacySweepMarkerKey();
|
|
903
|
+
const marker = await this.#acquireLegacySweep(markerKey);
|
|
904
|
+
if (marker === "done") return;
|
|
905
|
+
try {
|
|
906
|
+
for await (const { key, value } of this.#kv.list(this.#prefix)) {
|
|
907
|
+
if (key.length !== this.#prefix.length + 1) continue;
|
|
908
|
+
await this.#migrateLegacyState(key, value);
|
|
909
|
+
}
|
|
910
|
+
} catch (error) {
|
|
911
|
+
await this.#deleteIfUnchanged(markerKey, marker);
|
|
912
|
+
throw error;
|
|
913
|
+
}
|
|
914
|
+
const finishedMarker = this.#finishLegacySweepMarker(marker);
|
|
915
|
+
if (await this.#kv.cas(markerKey, marker, finishedMarker)) this.#rememberLegacySweepMarker(finishedMarker);
|
|
916
|
+
}
|
|
917
|
+
#finishLegacySweepMarker(marker) {
|
|
918
|
+
const retryUntil = getLegacySweepRetryUntil(marker);
|
|
919
|
+
if (retryUntil != null && Temporal.Instant.compare(this.#now(), Temporal.Instant.from(retryUntil)) < 0) return {
|
|
920
|
+
state: "done",
|
|
921
|
+
retryUntil
|
|
922
|
+
};
|
|
923
|
+
return { state: "final" };
|
|
924
|
+
}
|
|
925
|
+
async #migrateLegacyState(key, value) {
|
|
926
|
+
if (isCurrentCircuitBreakerState(value)) return;
|
|
927
|
+
const state = parseCircuitBreakerKvState(value);
|
|
928
|
+
if (state != null) {
|
|
929
|
+
await this.#kv.cas(key, value, markCircuitBreakerState(state), this.#setOptions());
|
|
930
|
+
return;
|
|
931
|
+
}
|
|
932
|
+
await this.#deleteIfUnchanged(key, value);
|
|
933
|
+
}
|
|
934
|
+
async #deleteIfUnchanged(key, value) {
|
|
935
|
+
if (await this.#kv.cas(key, value, LEGACY_SWEEP_DELETING_MARKER, { ttl: LEGACY_SWEEP_LOCK_TTL })) await this.#kv.delete(key);
|
|
936
|
+
}
|
|
937
|
+
async #acquireLegacySweep(markerKey) {
|
|
938
|
+
for (let attempt = 0; attempt < LEGACY_SWEEP_CAS_ATTEMPTS; attempt++) {
|
|
939
|
+
const marker = await this.#kv.get(markerKey);
|
|
940
|
+
const now = this.#now();
|
|
941
|
+
if (isLegacySweepActive(marker, now)) {
|
|
942
|
+
this.#rememberLegacySweepMarker(marker);
|
|
943
|
+
return "done";
|
|
944
|
+
}
|
|
945
|
+
const retryUntil = isLegacySweepRetryDue(marker, now) ? marker.retryUntil : getLegacySweepRetryUntil(marker) ?? now.add(LEGACY_SWEEP_RETRY_WINDOW).toString();
|
|
946
|
+
const sweeping = {
|
|
947
|
+
state: "sweeping",
|
|
948
|
+
started: now.toString(),
|
|
949
|
+
retryUntil
|
|
950
|
+
};
|
|
951
|
+
if (await this.#kv.cas(markerKey, marker ?? void 0, sweeping, { ttl: LEGACY_SWEEP_LOCK_TTL })) return sweeping;
|
|
952
|
+
await delay(LEGACY_SWEEP_WAIT_INTERVAL);
|
|
953
|
+
}
|
|
954
|
+
return "done";
|
|
955
|
+
}
|
|
956
|
+
#isLegacySweepDone() {
|
|
957
|
+
if (this.#legacySweepDone === "final") return true;
|
|
958
|
+
if (this.#legacySweepDone == null) return false;
|
|
959
|
+
if (Temporal.Instant.compare(this.#now(), this.#legacySweepDone) < 0) return true;
|
|
960
|
+
this.#legacySweepDone = void 0;
|
|
961
|
+
return false;
|
|
962
|
+
}
|
|
963
|
+
#rememberLegacySweepMarker(marker) {
|
|
964
|
+
const doneUntil = getLegacySweepDoneUntil(marker, this.#now());
|
|
965
|
+
if (doneUntil != null) this.#legacySweepDone = doneUntil;
|
|
966
|
+
}
|
|
861
967
|
#capHeldRetryAt(now, heldSince, retryAt) {
|
|
862
968
|
const expiresAt = (heldSince ?? now).add(this.#options.heldActivityTtl);
|
|
863
969
|
return Temporal.Instant.compare(expiresAt, retryAt) < 0 ? expiresAt : retryAt;
|
|
864
970
|
}
|
|
865
971
|
async #get(remoteHost) {
|
|
866
|
-
return
|
|
972
|
+
return parseStoredCircuitBreakerKvState(await this.#kv.get(this.#key(remoteHost)));
|
|
867
973
|
}
|
|
868
974
|
async #replace(remoteHost, oldState, newState) {
|
|
869
975
|
const key = this.#key(remoteHost);
|
|
976
|
+
const storedState = newState == null ? void 0 : markCircuitBreakerState(newState);
|
|
870
977
|
if (this.#kv.cas == null) {
|
|
871
|
-
if (
|
|
872
|
-
else await this.#kv.set(key,
|
|
978
|
+
if (storedState == null) await this.#kv.delete(key);
|
|
979
|
+
else await this.#kv.set(key, storedState, this.#setOptions());
|
|
873
980
|
return true;
|
|
874
981
|
}
|
|
875
|
-
return await this.#kv.cas(key, oldState,
|
|
982
|
+
return await this.#kv.cas(key, oldState, storedState, storedState == null ? void 0 : this.#setOptions());
|
|
983
|
+
}
|
|
984
|
+
#setOptions() {
|
|
985
|
+
return this.#options.stateTtl == null ? void 0 : { ttl: this.#options.stateTtl };
|
|
876
986
|
}
|
|
877
987
|
async #notifyStateChange(remoteHost, previousState, newState) {
|
|
878
988
|
try {
|
|
@@ -912,18 +1022,25 @@ var CircuitBreaker = class {
|
|
|
912
1022
|
* @param options The public circuit breaker options supplied to Fedify.
|
|
913
1023
|
* @returns The normalized failure predicate, failure pruning function,
|
|
914
1024
|
* duration values, and optional callbacks with defaults applied.
|
|
915
|
-
* @throws {RangeError} If any configured duration is not positive
|
|
1025
|
+
* @throws {RangeError} If any configured duration is not positive, or if
|
|
1026
|
+
* `stateTtl` is shorter than `recoveryDelay`.
|
|
916
1027
|
* @throws {TypeError} If `failureThreshold` is not a positive integer.
|
|
917
1028
|
*/
|
|
918
1029
|
function normalizeCircuitBreakerOptions(options) {
|
|
919
1030
|
const recoveryDelay = toInstantDuration(options.recoveryDelay ?? { minutes: 30 });
|
|
920
1031
|
const heldActivityTtl = toInstantDuration(options.heldActivityTtl ?? { hours: 168 });
|
|
921
1032
|
const releaseInterval = toInstantDuration(options.releaseInterval ?? { seconds: 1 });
|
|
1033
|
+
const configuredStateTtl = options.stateTtl == null ? void 0 : toInstantDuration(options.stateTtl);
|
|
922
1034
|
assertPositiveDuration(recoveryDelay, "recoveryDelay");
|
|
923
1035
|
assertPositiveDuration(heldActivityTtl, "heldActivityTtl");
|
|
924
1036
|
assertPositiveDuration(releaseInterval, "releaseInterval");
|
|
1037
|
+
if (configuredStateTtl != null) {
|
|
1038
|
+
assertPositiveDuration(configuredStateTtl, "stateTtl");
|
|
1039
|
+
if (Temporal.Duration.compare(configuredStateTtl, recoveryDelay) < 0) throw new RangeError("stateTtl must be at least recoveryDelay.");
|
|
1040
|
+
}
|
|
925
1041
|
let failure;
|
|
926
1042
|
let pruneFailures;
|
|
1043
|
+
let stateTtl;
|
|
927
1044
|
if (options.failure == null) {
|
|
928
1045
|
const failureThreshold = options.failureThreshold ?? 5;
|
|
929
1046
|
if (!Number.isInteger(failureThreshold) || failureThreshold <= 0) throw new TypeError("failureThreshold must be a positive integer.");
|
|
@@ -939,9 +1056,11 @@ function normalizeCircuitBreakerOptions(options) {
|
|
|
939
1056
|
const last = timestamps[timestamps.length - 1];
|
|
940
1057
|
return Temporal.Duration.compare(first.until(last), failureWindow) <= 0;
|
|
941
1058
|
};
|
|
1059
|
+
stateTtl = configuredStateTtl ?? maxDuration(recoveryDelay.add(failureWindow), recoveryDelay.add(heldActivityTtl));
|
|
942
1060
|
} else {
|
|
943
1061
|
failure = options.failure;
|
|
944
1062
|
pruneFailures = (timestamps) => timestamps.slice(-MAX_CUSTOM_FAILURE_HISTORY);
|
|
1063
|
+
stateTtl = configuredStateTtl;
|
|
945
1064
|
}
|
|
946
1065
|
return {
|
|
947
1066
|
failure,
|
|
@@ -949,10 +1068,72 @@ function normalizeCircuitBreakerOptions(options) {
|
|
|
949
1068
|
recoveryDelay,
|
|
950
1069
|
heldActivityTtl,
|
|
951
1070
|
releaseInterval,
|
|
1071
|
+
stateTtl,
|
|
952
1072
|
onStateChange: options.onStateChange,
|
|
953
1073
|
onActivityDrop: options.onActivityDrop
|
|
954
1074
|
};
|
|
955
1075
|
}
|
|
1076
|
+
function maxDuration(duration, ...durations) {
|
|
1077
|
+
return durations.reduce((max, candidate) => Temporal.Duration.compare(candidate, max) > 0 ? candidate : max, duration);
|
|
1078
|
+
}
|
|
1079
|
+
function isLegacySweepDone(value, now) {
|
|
1080
|
+
return getLegacySweepDoneUntil(value, now) != null;
|
|
1081
|
+
}
|
|
1082
|
+
function getLegacySweepDoneUntil(value, now) {
|
|
1083
|
+
if (typeof value !== "object" || value == null || !("state" in value)) return;
|
|
1084
|
+
if (value.state === "final") return "final";
|
|
1085
|
+
if (value.state === "done" && "retryUntil" in value && typeof value.retryUntil === "string") try {
|
|
1086
|
+
const retryUntil = Temporal.Instant.from(value.retryUntil);
|
|
1087
|
+
return Temporal.Instant.compare(now, retryUntil) < 0 ? retryUntil : void 0;
|
|
1088
|
+
} catch {
|
|
1089
|
+
return;
|
|
1090
|
+
}
|
|
1091
|
+
}
|
|
1092
|
+
function isLegacySweepRetryDue(value, now) {
|
|
1093
|
+
if (typeof value !== "object" || value == null || !("state" in value) || value.state !== "done" || !("retryUntil" in value) || typeof value.retryUntil !== "string") return false;
|
|
1094
|
+
try {
|
|
1095
|
+
return Temporal.Instant.compare(now, Temporal.Instant.from(value.retryUntil)) >= 0;
|
|
1096
|
+
} catch {
|
|
1097
|
+
return false;
|
|
1098
|
+
}
|
|
1099
|
+
}
|
|
1100
|
+
function isLegacySweepActive(value, now) {
|
|
1101
|
+
return isLegacySweepDone(value, now) || isLegacySweepInProgress(value, now);
|
|
1102
|
+
}
|
|
1103
|
+
function isLegacySweepInProgress(value, now) {
|
|
1104
|
+
if (typeof value !== "object" || value == null || !("state" in value) || value.state !== "sweeping" || !("started" in value) || typeof value.started !== "string") return false;
|
|
1105
|
+
try {
|
|
1106
|
+
return Temporal.Instant.compare(now, Temporal.Instant.from(value.started).add(LEGACY_SWEEP_LOCK_TTL)) < 0;
|
|
1107
|
+
} catch {
|
|
1108
|
+
return false;
|
|
1109
|
+
}
|
|
1110
|
+
}
|
|
1111
|
+
function getLegacySweepRetryUntil(value) {
|
|
1112
|
+
if (typeof value !== "object" || value == null || !("retryUntil" in value) || typeof value.retryUntil !== "string") return;
|
|
1113
|
+
try {
|
|
1114
|
+
Temporal.Instant.from(value.retryUntil);
|
|
1115
|
+
return value.retryUntil;
|
|
1116
|
+
} catch {
|
|
1117
|
+
return;
|
|
1118
|
+
}
|
|
1119
|
+
}
|
|
1120
|
+
function isCurrentCircuitBreakerState(value) {
|
|
1121
|
+
return typeof value === "object" && value != null && "__fedifyCircuitBreakerStateVersion" in value && value.__fedifyCircuitBreakerStateVersion === CIRCUIT_BREAKER_STATE_VERSION;
|
|
1122
|
+
}
|
|
1123
|
+
function markCircuitBreakerState(state) {
|
|
1124
|
+
return {
|
|
1125
|
+
...state,
|
|
1126
|
+
__fedifyCircuitBreakerStateVersion: CIRCUIT_BREAKER_STATE_VERSION
|
|
1127
|
+
};
|
|
1128
|
+
}
|
|
1129
|
+
function stripStoredCircuitBreakerState(state) {
|
|
1130
|
+
if (state == null) return void 0;
|
|
1131
|
+
const { __fedifyCircuitBreakerStateVersion: _, ...publicState } = state;
|
|
1132
|
+
return publicState;
|
|
1133
|
+
}
|
|
1134
|
+
function delay(milliseconds) {
|
|
1135
|
+
return new Promise((resolve) => setTimeout(resolve, milliseconds));
|
|
1136
|
+
}
|
|
956
1137
|
function toInstantDuration(duration) {
|
|
957
1138
|
const parsed = Temporal.Duration.from(duration);
|
|
958
1139
|
return Temporal.Duration.from({ milliseconds: Math.trunc(parsed.total({
|
|
@@ -987,6 +1168,9 @@ function parseHeldSince(value) {
|
|
|
987
1168
|
* valid instant strings, or `undefined` when the stored value is malformed.
|
|
988
1169
|
*/
|
|
989
1170
|
function parseCircuitBreakerKvState(value) {
|
|
1171
|
+
return stripStoredCircuitBreakerState(parseStoredCircuitBreakerKvState(value));
|
|
1172
|
+
}
|
|
1173
|
+
function parseStoredCircuitBreakerKvState(value) {
|
|
990
1174
|
const isInstantString = (v) => {
|
|
991
1175
|
if (typeof v !== "string") return false;
|
|
992
1176
|
try {
|
|
@@ -1006,7 +1190,8 @@ function parseCircuitBreakerKvState(value) {
|
|
|
1006
1190
|
state: record.state,
|
|
1007
1191
|
failures: record.failures,
|
|
1008
1192
|
...record.opened == null ? {} : { opened: record.opened },
|
|
1009
|
-
...record.halfOpened == null ? {} : { halfOpened: record.halfOpened }
|
|
1193
|
+
...record.halfOpened == null ? {} : { halfOpened: record.halfOpened },
|
|
1194
|
+
...record.__fedifyCircuitBreakerStateVersion === CIRCUIT_BREAKER_STATE_VERSION ? { __fedifyCircuitBreakerStateVersion: CIRCUIT_BREAKER_STATE_VERSION } : {}
|
|
1010
1195
|
};
|
|
1011
1196
|
}
|
|
1012
1197
|
//#endregion
|
|
@@ -3558,6 +3743,24 @@ const logger = getLogger([
|
|
|
3558
3743
|
"webfinger",
|
|
3559
3744
|
"server"
|
|
3560
3745
|
]);
|
|
3746
|
+
function getWebFingerSubjectAndAliases({ resourceUrl, actorUri, preferredUsername, acctUsername, host, contextHost }) {
|
|
3747
|
+
const aliases = [];
|
|
3748
|
+
let subject = resourceUrl.href;
|
|
3749
|
+
if (resourceUrl.protocol != "acct:" && preferredUsername != null) {
|
|
3750
|
+
aliases.push(`acct:${preferredUsername}@${host ?? contextHost}`);
|
|
3751
|
+
if (host != null && host !== contextHost) aliases.push(`acct:${preferredUsername}@${contextHost}`);
|
|
3752
|
+
}
|
|
3753
|
+
if (resourceUrl.href !== actorUri.href) aliases.push(actorUri.href);
|
|
3754
|
+
if (resourceUrl.protocol === "acct:" && host != null && host !== contextHost && !resourceUrl.href.endsWith(`@${host}`)) {
|
|
3755
|
+
subject = `acct:${preferredUsername ?? acctUsername}@${host}`;
|
|
3756
|
+
aliases.push(resourceUrl.href);
|
|
3757
|
+
if (preferredUsername != null && preferredUsername !== "" && preferredUsername !== acctUsername) aliases.push(`acct:${preferredUsername}@${contextHost}`);
|
|
3758
|
+
}
|
|
3759
|
+
return {
|
|
3760
|
+
subject,
|
|
3761
|
+
aliases
|
|
3762
|
+
};
|
|
3763
|
+
}
|
|
3561
3764
|
/**
|
|
3562
3765
|
* Handles a WebFinger request. You would not typically call this function
|
|
3563
3766
|
* directly, but instead use {@link Federation.fetch} method.
|
|
@@ -3662,6 +3865,7 @@ async function handleWebFingerInternal(request, { context, host, actorDispatcher
|
|
|
3662
3865
|
return identifier;
|
|
3663
3866
|
}
|
|
3664
3867
|
let identifier = null;
|
|
3868
|
+
let acctUsername = null;
|
|
3665
3869
|
const uriParsed = context.parseUri(resourceUrl);
|
|
3666
3870
|
if (uriParsed?.type != "actor") {
|
|
3667
3871
|
const match = /^acct:([^@]+)@([^@]+)$/.exec(resource);
|
|
@@ -3675,8 +3879,9 @@ async function handleWebFingerInternal(request, { context, host, actorDispatcher
|
|
|
3675
3879
|
const normalizedHost = portMatch == null ? domainToASCII(match[2].toLowerCase()) : domainToASCII(match[2].substring(0, portMatch.index).toLowerCase()) + portMatch[0];
|
|
3676
3880
|
if (normalizedHost != context.url.host && normalizedHost != host) return await onNotFound(request);
|
|
3677
3881
|
else {
|
|
3678
|
-
|
|
3679
|
-
|
|
3882
|
+
acctUsername = match[1];
|
|
3883
|
+
identifier = await mapUsernameToIdentifier(acctUsername);
|
|
3884
|
+
resourceUrl = new URL(`acct:${acctUsername}@${normalizedHost}`);
|
|
3680
3885
|
}
|
|
3681
3886
|
}
|
|
3682
3887
|
} else identifier = uriParsed.identifier;
|
|
@@ -3690,9 +3895,10 @@ async function handleWebFingerInternal(request, { context, host, actorDispatcher
|
|
|
3690
3895
|
status: 410,
|
|
3691
3896
|
headers: { "Access-Control-Allow-Origin": "*" }
|
|
3692
3897
|
});
|
|
3898
|
+
const actorUri = context.getActorUri(identifier);
|
|
3693
3899
|
const links = [{
|
|
3694
3900
|
rel: "self",
|
|
3695
|
-
href:
|
|
3901
|
+
href: actorUri.href,
|
|
3696
3902
|
type: "application/activity+json"
|
|
3697
3903
|
}];
|
|
3698
3904
|
for (const url of actor.urls) if (url instanceof Link && url.href != null) links.push({
|
|
@@ -3716,23 +3922,19 @@ async function handleWebFingerInternal(request, { context, host, actorDispatcher
|
|
|
3716
3922
|
const customLinks = await webFingerLinksDispatcher(context, resourceUrl);
|
|
3717
3923
|
if (customLinks != null) for (const link of customLinks) links.push(link);
|
|
3718
3924
|
}
|
|
3719
|
-
const aliases =
|
|
3720
|
-
|
|
3721
|
-
|
|
3722
|
-
|
|
3723
|
-
|
|
3724
|
-
|
|
3725
|
-
|
|
3726
|
-
|
|
3727
|
-
|
|
3728
|
-
|
|
3729
|
-
}
|
|
3730
|
-
const jrd = {
|
|
3731
|
-
subject: resourceUrl.href,
|
|
3925
|
+
const { subject, aliases } = getWebFingerSubjectAndAliases({
|
|
3926
|
+
resourceUrl,
|
|
3927
|
+
actorUri,
|
|
3928
|
+
preferredUsername: actor.preferredUsername,
|
|
3929
|
+
acctUsername,
|
|
3930
|
+
host,
|
|
3931
|
+
contextHost: context.url.host
|
|
3932
|
+
});
|
|
3933
|
+
return new Response(JSON.stringify({
|
|
3934
|
+
subject,
|
|
3732
3935
|
aliases,
|
|
3733
3936
|
links
|
|
3734
|
-
}
|
|
3735
|
-
return new Response(JSON.stringify(jrd), { headers: {
|
|
3937
|
+
}), { headers: {
|
|
3736
3938
|
"Content-Type": "application/jrd+json",
|
|
3737
3939
|
"Access-Control-Allow-Origin": "*"
|
|
3738
3940
|
} });
|
|
@@ -1,26 +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, b as recordWebFingerHandle, c as recordCircuitBreakerStateChange, d as recordCollectionRequest, f as recordCollectionTotalItems, g as recordInboxActivity, h as recordFanoutRecipients, i as getRemoteHost, l as recordCollectionDispatchDuration, n as getDurationMs, o as isAbortError, r as getFederationMetrics, u as recordCollectionPageItems, v as recordOutboxActivity, x as registerQueueDepthGauge, y as recordOutboxEnqueue } from "./metrics-
|
|
4
|
+
import { n as version, t as name } from "./deno-B8lcQ4Ms.mjs";
|
|
5
|
+
import { a as instrumentDocumentLoader, b as recordWebFingerHandle, c as recordCircuitBreakerStateChange, d as recordCollectionRequest, f as recordCollectionTotalItems, g as recordInboxActivity, h as recordFanoutRecipients, i as getRemoteHost, l as recordCollectionDispatchDuration, n as getDurationMs, o as isAbortError, r as getFederationMetrics, u as recordCollectionPageItems, v as recordOutboxActivity, x as registerQueueDepthGauge, y as recordOutboxEnqueue } from "./metrics-DxJzdAgZ.mjs";
|
|
6
6
|
import { t as formatAcceptSignature } from "./accept-CPkZzmGN.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 { _ 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-
|
|
12
|
-
import { n as getKeyOwner, t as doesActorOwnKey } from "./owner-
|
|
7
|
+
import { a as importJwk, o as validateCryptoKey, t as exportJwk } from "./key-BqT9YrM3.mjs";
|
|
8
|
+
import { l as verifyRequest, o as parseRfc9421SignatureInput, u as verifyRequestDetailed } from "./http-DlSQMJsb.mjs";
|
|
9
|
+
import { t as getAuthenticatedDocumentLoader } from "./docloader-BMjxpyJD.mjs";
|
|
10
|
+
import { n as kvCache } from "./kv-cache-BD6BgCTF.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-hwRDfZqq.mjs";
|
|
12
|
+
import { n as getKeyOwner, t as doesActorOwnKey } from "./owner-Btzxz2vj.mjs";
|
|
13
13
|
import { r as normalizeOutgoingActivityJsonLd } from "./outgoing-jsonld-L_DbOaFe.mjs";
|
|
14
|
-
import { i as verifyObject, n as hasProofLike, r as signObject } from "./proof-
|
|
15
|
-
import { t as getNodeInfo } from "./client-
|
|
14
|
+
import { i as verifyObject, n as hasProofLike, r as signObject } from "./proof-D2Z0Wa9X.mjs";
|
|
15
|
+
import { t as getNodeInfo } from "./client-D1KuWxjH.mjs";
|
|
16
16
|
import { t as nodeInfoToJson } from "./types-J53Kw7so.mjs";
|
|
17
|
-
import { n as extractInboxes, r as sendActivity, t as SendActivityError } from "./send-
|
|
18
|
-
import { n as FederationBuilderImpl, t as ACTOR_ALIAS_PREFIX } from "./builder-
|
|
19
|
-
import { t as CircuitBreaker } from "./circuit-breaker-
|
|
17
|
+
import { n as extractInboxes, r as sendActivity, t as SendActivityError } from "./send-BljvjEOa.mjs";
|
|
18
|
+
import { n as FederationBuilderImpl, t as ACTOR_ALIAS_PREFIX } from "./builder-Dx4EI2YS.mjs";
|
|
19
|
+
import { t as CircuitBreaker } from "./circuit-breaker-DLS_n1x5.mjs";
|
|
20
20
|
import { t as buildCollectionSynchronizationHeader } from "./collection-Cc3DVAhE.mjs";
|
|
21
21
|
import { t as KvKeyCache } from "./keycache-BeU0LCII.mjs";
|
|
22
22
|
import { t as acceptsJsonLd } from "./negotiation-DDstyBvc.mjs";
|
|
23
|
-
import { t as hasMalformedKnownTemporalLiteral } from "./temporal-
|
|
23
|
+
import { t as hasMalformedKnownTemporalLiteral } from "./temporal-Dg8Hb3lz.mjs";
|
|
24
24
|
import { t as createExponentialBackoffPolicy } from "./retry-CXg_MBI-.mjs";
|
|
25
25
|
import { getLogger, withContext } from "@logtape/logtape";
|
|
26
26
|
import { RouterError } from "@fedify/uri-template";
|
|
@@ -2099,6 +2099,24 @@ const logger = getLogger([
|
|
|
2099
2099
|
"webfinger",
|
|
2100
2100
|
"server"
|
|
2101
2101
|
]);
|
|
2102
|
+
function getWebFingerSubjectAndAliases({ resourceUrl, actorUri, preferredUsername, acctUsername, host, contextHost }) {
|
|
2103
|
+
const aliases = [];
|
|
2104
|
+
let subject = resourceUrl.href;
|
|
2105
|
+
if (resourceUrl.protocol != "acct:" && preferredUsername != null) {
|
|
2106
|
+
aliases.push(`acct:${preferredUsername}@${host ?? contextHost}`);
|
|
2107
|
+
if (host != null && host !== contextHost) aliases.push(`acct:${preferredUsername}@${contextHost}`);
|
|
2108
|
+
}
|
|
2109
|
+
if (resourceUrl.href !== actorUri.href) aliases.push(actorUri.href);
|
|
2110
|
+
if (resourceUrl.protocol === "acct:" && host != null && host !== contextHost && !resourceUrl.href.endsWith(`@${host}`)) {
|
|
2111
|
+
subject = `acct:${preferredUsername ?? acctUsername}@${host}`;
|
|
2112
|
+
aliases.push(resourceUrl.href);
|
|
2113
|
+
if (preferredUsername != null && preferredUsername !== "" && preferredUsername !== acctUsername) aliases.push(`acct:${preferredUsername}@${contextHost}`);
|
|
2114
|
+
}
|
|
2115
|
+
return {
|
|
2116
|
+
subject,
|
|
2117
|
+
aliases
|
|
2118
|
+
};
|
|
2119
|
+
}
|
|
2102
2120
|
/**
|
|
2103
2121
|
* Handles a WebFinger request. You would not typically call this function
|
|
2104
2122
|
* directly, but instead use {@link Federation.fetch} method.
|
|
@@ -2203,6 +2221,7 @@ async function handleWebFingerInternal(request, { context, host, actorDispatcher
|
|
|
2203
2221
|
return identifier;
|
|
2204
2222
|
}
|
|
2205
2223
|
let identifier = null;
|
|
2224
|
+
let acctUsername = null;
|
|
2206
2225
|
const uriParsed = context.parseUri(resourceUrl);
|
|
2207
2226
|
if (uriParsed?.type != "actor") {
|
|
2208
2227
|
const match = /^acct:([^@]+)@([^@]+)$/.exec(resource);
|
|
@@ -2216,8 +2235,9 @@ async function handleWebFingerInternal(request, { context, host, actorDispatcher
|
|
|
2216
2235
|
const normalizedHost = portMatch == null ? domainToASCII(match[2].toLowerCase()) : domainToASCII(match[2].substring(0, portMatch.index).toLowerCase()) + portMatch[0];
|
|
2217
2236
|
if (normalizedHost != context.url.host && normalizedHost != host) return await onNotFound(request);
|
|
2218
2237
|
else {
|
|
2219
|
-
|
|
2220
|
-
|
|
2238
|
+
acctUsername = match[1];
|
|
2239
|
+
identifier = await mapUsernameToIdentifier(acctUsername);
|
|
2240
|
+
resourceUrl = new URL(`acct:${acctUsername}@${normalizedHost}`);
|
|
2221
2241
|
}
|
|
2222
2242
|
}
|
|
2223
2243
|
} else identifier = uriParsed.identifier;
|
|
@@ -2231,9 +2251,10 @@ async function handleWebFingerInternal(request, { context, host, actorDispatcher
|
|
|
2231
2251
|
status: 410,
|
|
2232
2252
|
headers: { "Access-Control-Allow-Origin": "*" }
|
|
2233
2253
|
});
|
|
2254
|
+
const actorUri = context.getActorUri(identifier);
|
|
2234
2255
|
const links = [{
|
|
2235
2256
|
rel: "self",
|
|
2236
|
-
href:
|
|
2257
|
+
href: actorUri.href,
|
|
2237
2258
|
type: "application/activity+json"
|
|
2238
2259
|
}];
|
|
2239
2260
|
for (const url of actor.urls) if (url instanceof Link && url.href != null) links.push({
|
|
@@ -2257,23 +2278,19 @@ async function handleWebFingerInternal(request, { context, host, actorDispatcher
|
|
|
2257
2278
|
const customLinks = await webFingerLinksDispatcher(context, resourceUrl);
|
|
2258
2279
|
if (customLinks != null) for (const link of customLinks) links.push(link);
|
|
2259
2280
|
}
|
|
2260
|
-
const aliases =
|
|
2261
|
-
|
|
2262
|
-
|
|
2263
|
-
|
|
2264
|
-
|
|
2265
|
-
|
|
2266
|
-
|
|
2267
|
-
|
|
2268
|
-
|
|
2269
|
-
|
|
2270
|
-
}
|
|
2271
|
-
const jrd = {
|
|
2272
|
-
subject: resourceUrl.href,
|
|
2281
|
+
const { subject, aliases } = getWebFingerSubjectAndAliases({
|
|
2282
|
+
resourceUrl,
|
|
2283
|
+
actorUri,
|
|
2284
|
+
preferredUsername: actor.preferredUsername,
|
|
2285
|
+
acctUsername,
|
|
2286
|
+
host,
|
|
2287
|
+
contextHost: context.url.host
|
|
2288
|
+
});
|
|
2289
|
+
return new Response(JSON.stringify({
|
|
2290
|
+
subject,
|
|
2273
2291
|
aliases,
|
|
2274
2292
|
links
|
|
2275
|
-
}
|
|
2276
|
-
return new Response(JSON.stringify(jrd), { headers: {
|
|
2293
|
+
}), { headers: {
|
|
2277
2294
|
"Content-Type": "application/jrd+json",
|
|
2278
2295
|
"Access-Control-Allow-Origin": "*"
|
|
2279
2296
|
} });
|