@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.
Files changed (69) hide show
  1. package/README.md +11 -8
  2. package/dist/{builder-hivpmhlD.mjs → builder-Dx4EI2YS.mjs} +4 -2
  3. package/dist/{circuit-breaker-CSWsyoef.mjs → circuit-breaker-DLS_n1x5.mjs} +190 -7
  4. package/dist/circuit-breaker-test-utils-BVnv3T76.mjs +14 -0
  5. package/dist/{client-ByXmQhYD.mjs → client-D1KuWxjH.mjs} +63 -5
  6. package/dist/compat/mod.d.cts +1 -1
  7. package/dist/compat/mod.d.ts +1 -1
  8. package/dist/compat/transformers.test.mjs +1 -1
  9. package/dist/{context-BBVLF7lx.d.cts → context-Bjswu3eC.d.cts} +18 -1
  10. package/dist/{context-BU6jSQdo.d.ts → context-COxc_O-c.d.ts} +18 -1
  11. package/dist/{deno-C8RG0HM3.mjs → deno-B8lcQ4Ms.mjs} +1 -1
  12. package/dist/{docloader-YZA0m_3O.mjs → docloader-BMjxpyJD.mjs} +2 -2
  13. package/dist/federation/builder.test.mjs +13 -1
  14. package/dist/federation/circuit-breaker.test.mjs +622 -3
  15. package/dist/federation/handler.test.mjs +3 -3
  16. package/dist/federation/idempotency.test.mjs +2 -2
  17. package/dist/federation/metrics.test.mjs +1 -1
  18. package/dist/federation/middleware.test.mjs +16 -8
  19. package/dist/federation/mod.cjs +1 -1
  20. package/dist/federation/mod.d.cts +2 -2
  21. package/dist/federation/mod.d.ts +2 -2
  22. package/dist/federation/mod.js +1 -1
  23. package/dist/federation/send.test.mjs +3 -3
  24. package/dist/federation/temporal.test.mjs +1 -1
  25. package/dist/federation/webfinger.test.mjs +69 -7
  26. package/dist/{http-BajqV0pH.cjs → http-CpdQD2ke.cjs} +1 -1
  27. package/dist/{http-DPOMa1DD.js → http-DVZxcZEr.js} +1 -1
  28. package/dist/{http-PCt0862M.mjs → http-DlSQMJsb.mjs} +3 -3
  29. package/dist/{key-BKYwmbur.mjs → key-BqT9YrM3.mjs} +2 -2
  30. package/dist/{kv-cache-IjRceERu.js → kv-cache-B52kTPj4.js} +1 -1
  31. package/dist/{kv-cache-CVG3m2_-.cjs → kv-cache-B8gm4cVB.cjs} +1 -1
  32. package/dist/{kv-cache-Ceg0c1wh.mjs → kv-cache-BD6BgCTF.mjs} +1 -1
  33. package/dist/{ld-Dcw2skfn.mjs → ld-hwRDfZqq.mjs} +3 -3
  34. package/dist/{metrics-Pue1x4ta.mjs → metrics-DxJzdAgZ.mjs} +1 -1
  35. package/dist/{middleware-By8lqc18.js → middleware-B2rDMvhv.js} +231 -29
  36. package/dist/{middleware-WU_5fbGS.mjs → middleware-CubmqVku.mjs} +49 -32
  37. package/dist/{middleware-BiXEAbn2.cjs → middleware-D2F1jSer.cjs} +231 -29
  38. package/dist/{middleware-CyxqoeW9.mjs → middleware-DxCKBFFT.mjs} +1 -1
  39. package/dist/{mod-C0F6kvgS.d.cts → mod-0bIqGrKQ.d.cts} +1 -1
  40. package/dist/{mod-vPYVoa5n.d.ts → mod-3H9Gym5G.d.ts} +1 -1
  41. package/dist/mod.cjs +5 -5
  42. package/dist/mod.d.cts +2 -2
  43. package/dist/mod.d.ts +2 -2
  44. package/dist/mod.js +5 -5
  45. package/dist/nodeinfo/client.test.mjs +61 -1
  46. package/dist/nodeinfo/handler.test.mjs +1 -1
  47. package/dist/nodeinfo/mod.cjs +1 -1
  48. package/dist/nodeinfo/mod.js +1 -1
  49. package/dist/{owner-pVJ0Twi7.mjs → owner-Btzxz2vj.mjs} +2 -2
  50. package/dist/{proof-HzavUYxN.cjs → proof-BdnNZ9OM.cjs} +1 -1
  51. package/dist/{proof-DA8Kbchf.js → proof-C_kcg7vF.js} +1 -1
  52. package/dist/{proof-pXYODmNV.mjs → proof-D2Z0Wa9X.mjs} +3 -3
  53. package/dist/{send-BfAp60BJ.mjs → send-BljvjEOa.mjs} +3 -3
  54. package/dist/sig/http.test.mjs +2 -2
  55. package/dist/sig/key.test.mjs +1 -1
  56. package/dist/sig/ld.test.mjs +2 -2
  57. package/dist/sig/mod.cjs +2 -2
  58. package/dist/sig/mod.js +2 -2
  59. package/dist/sig/owner.test.mjs +1 -1
  60. package/dist/sig/proof.test.mjs +1 -1
  61. package/dist/{temporal-HZHBbbk4.mjs → temporal-Dg8Hb3lz.mjs} +1 -1
  62. package/dist/{types-CAY3OdLq.js → types-DTONwNqL.js} +63 -5
  63. package/dist/{types-KC4QAoxe.cjs → types-m8u9L86X.cjs} +62 -4
  64. package/dist/utils/docloader.test.mjs +2 -2
  65. package/dist/utils/kv-cache.test.mjs +1 -1
  66. package/dist/utils/mod.cjs +1 -1
  67. package/dist/utils/mod.js +1 -1
  68. package/package.json +6 -6
  69. package/skills/fedify/SKILL.md +462 -0
@@ -2,10 +2,10 @@ const { Temporal } = require("@js-temporal/polyfill");
2
2
  const { URLPattern } = require("urlpattern-polyfill");
3
3
  const require_chunk = require("./chunk-DDcVe30Y.cjs");
4
4
  const require_transformers = require("./transformers-NeAONrAq.cjs");
5
- const require_http = require("./http-BajqV0pH.cjs");
6
- const require_proof = require("./proof-HzavUYxN.cjs");
7
- const require_types = require("./types-KC4QAoxe.cjs");
8
- const require_kv_cache = require("./kv-cache-CVG3m2_-.cjs");
5
+ const require_http = require("./http-CpdQD2ke.cjs");
6
+ const require_proof = require("./proof-BdnNZ9OM.cjs");
7
+ const require_types = require("./types-m8u9L86X.cjs");
8
+ const require_kv_cache = require("./kv-cache-B8gm4cVB.cjs");
9
9
  let _logtape_logtape = require("@logtape/logtape");
10
10
  let _fedify_uri_template = require("@fedify/uri-template");
11
11
  let _fedify_vocab = require("@fedify/vocab");
@@ -138,6 +138,8 @@ var FederationBuilderImpl = class {
138
138
  f.webFingerLinksDispatcher = this.webFingerLinksDispatcher;
139
139
  f.objectCallbacks = { ...this.objectCallbacks };
140
140
  f.objectTypeIds = { ...this.objectTypeIds };
141
+ f.collectionCallbacks = { ...this.collectionCallbacks };
142
+ f.collectionTypeIds = { ...this.collectionTypeIds };
141
143
  f.inboxPath = this.inboxPath;
142
144
  f.outboxPath = this.outboxPath;
143
145
  f.inboxCallbacks = this.inboxCallbacks == null ? void 0 : { ...this.inboxCallbacks };
@@ -681,6 +683,13 @@ function createFederationBuilder() {
681
683
  //#endregion
682
684
  //#region src/federation/circuit-breaker.ts
683
685
  const MAX_CUSTOM_FAILURE_HISTORY = 100;
686
+ const LEGACY_SWEEP_MARKER = ["__fedify_meta", "circuit_breaker_state_ttl_sweep_v1"];
687
+ const LEGACY_SWEEP_DELETING_MARKER = { __fedifyDeletingCircuitBreakerLegacyState: true };
688
+ const CIRCUIT_BREAKER_STATE_VERSION = 1;
689
+ const LEGACY_SWEEP_LOCK_TTL = Temporal.Duration.from({ minutes: 5 });
690
+ const LEGACY_SWEEP_RETRY_WINDOW = Temporal.Duration.from({ hours: 168 });
691
+ const LEGACY_SWEEP_WAIT_INTERVAL = 10;
692
+ const LEGACY_SWEEP_CAS_ATTEMPTS = 3;
684
693
  /**
685
694
  * Tracks reachability state for remote outbox delivery hosts.
686
695
  * @since 2.3.0
@@ -690,6 +699,8 @@ var CircuitBreaker = class {
690
699
  #prefix;
691
700
  #options;
692
701
  #now;
702
+ #legacySweep;
703
+ #legacySweepDone;
693
704
  #stateChangeObserver;
694
705
  constructor(options) {
695
706
  this.#kv = options.kv;
@@ -706,6 +717,7 @@ var CircuitBreaker = class {
706
717
  return now.until(this.#capHeldRetryAt(now, heldSince, now.add(delay)));
707
718
  }
708
719
  async beforeSend(remoteHost, message) {
720
+ this.#sweepLegacyStates();
709
721
  const heldSince = parseHeldSince(message.circuitHeldSince);
710
722
  const now = this.#now();
711
723
  if (heldSince != null && Temporal.Instant.compare(heldSince.add(this.#options.heldActivityTtl), now) <= 0) return {
@@ -787,6 +799,7 @@ var CircuitBreaker = class {
787
799
  throw new Error(`Failed to update circuit breaker state for ${remoteHost}`);
788
800
  }
789
801
  async recordSuccess(remoteHost) {
802
+ this.#sweepLegacyStates();
790
803
  for (let attempt = 0; attempt < 10; attempt++) {
791
804
  const oldState = await this.#get(remoteHost);
792
805
  if (oldState == null) return void 0;
@@ -807,6 +820,7 @@ var CircuitBreaker = class {
807
820
  return await this.recordSuccess(remoteHost);
808
821
  }
809
822
  async recordFailure(remoteHost) {
823
+ this.#sweepLegacyStates();
810
824
  const now = this.#now();
811
825
  for (let attempt = 0; attempt < 10; attempt++) {
812
826
  const oldState = await this.#get(remoteHost);
@@ -854,26 +868,122 @@ var CircuitBreaker = class {
854
868
  }
855
869
  }
856
870
  async getState(remoteHost) {
857
- return await this.#get(remoteHost);
871
+ this.#sweepLegacyStates();
872
+ return stripStoredCircuitBreakerState(await this.#get(remoteHost));
873
+ }
874
+ /**
875
+ * The currently running background legacy sweep, if any.
876
+ * @internal
877
+ */
878
+ get pendingSweep() {
879
+ return this.#legacySweep;
858
880
  }
859
881
  #key(remoteHost) {
860
882
  return [...this.#prefix, remoteHost];
861
883
  }
884
+ #legacySweepMarkerKey() {
885
+ return [...this.#prefix, ...LEGACY_SWEEP_MARKER];
886
+ }
887
+ #sweepLegacyStates() {
888
+ if (this.#kv.cas == null) return;
889
+ if (this.#options.stateTtl == null) return;
890
+ if (this.#legacySweep != null) return;
891
+ if (this.#isLegacySweepDone()) return;
892
+ this.#legacySweep = this.#sweepLegacyStatesImpl().catch((error) => {
893
+ (0, _logtape_logtape.getLogger)([
894
+ "fedify",
895
+ "federation",
896
+ "circuit"
897
+ ]).warn("Failed to sweep legacy circuit breaker state:\n{error}", { error });
898
+ }).finally(() => {
899
+ this.#legacySweep = void 0;
900
+ });
901
+ }
902
+ async #sweepLegacyStatesImpl() {
903
+ const markerKey = this.#legacySweepMarkerKey();
904
+ const marker = await this.#acquireLegacySweep(markerKey);
905
+ if (marker === "done") return;
906
+ try {
907
+ for await (const { key, value } of this.#kv.list(this.#prefix)) {
908
+ if (key.length !== this.#prefix.length + 1) continue;
909
+ await this.#migrateLegacyState(key, value);
910
+ }
911
+ } catch (error) {
912
+ await this.#deleteIfUnchanged(markerKey, marker);
913
+ throw error;
914
+ }
915
+ const finishedMarker = this.#finishLegacySweepMarker(marker);
916
+ if (await this.#kv.cas(markerKey, marker, finishedMarker)) this.#rememberLegacySweepMarker(finishedMarker);
917
+ }
918
+ #finishLegacySweepMarker(marker) {
919
+ const retryUntil = getLegacySweepRetryUntil(marker);
920
+ if (retryUntil != null && Temporal.Instant.compare(this.#now(), Temporal.Instant.from(retryUntil)) < 0) return {
921
+ state: "done",
922
+ retryUntil
923
+ };
924
+ return { state: "final" };
925
+ }
926
+ async #migrateLegacyState(key, value) {
927
+ if (isCurrentCircuitBreakerState(value)) return;
928
+ const state = parseCircuitBreakerKvState(value);
929
+ if (state != null) {
930
+ await this.#kv.cas(key, value, markCircuitBreakerState(state), this.#setOptions());
931
+ return;
932
+ }
933
+ await this.#deleteIfUnchanged(key, value);
934
+ }
935
+ async #deleteIfUnchanged(key, value) {
936
+ if (await this.#kv.cas(key, value, LEGACY_SWEEP_DELETING_MARKER, { ttl: LEGACY_SWEEP_LOCK_TTL })) await this.#kv.delete(key);
937
+ }
938
+ async #acquireLegacySweep(markerKey) {
939
+ for (let attempt = 0; attempt < LEGACY_SWEEP_CAS_ATTEMPTS; attempt++) {
940
+ const marker = await this.#kv.get(markerKey);
941
+ const now = this.#now();
942
+ if (isLegacySweepActive(marker, now)) {
943
+ this.#rememberLegacySweepMarker(marker);
944
+ return "done";
945
+ }
946
+ const retryUntil = isLegacySweepRetryDue(marker, now) ? marker.retryUntil : getLegacySweepRetryUntil(marker) ?? now.add(LEGACY_SWEEP_RETRY_WINDOW).toString();
947
+ const sweeping = {
948
+ state: "sweeping",
949
+ started: now.toString(),
950
+ retryUntil
951
+ };
952
+ if (await this.#kv.cas(markerKey, marker ?? void 0, sweeping, { ttl: LEGACY_SWEEP_LOCK_TTL })) return sweeping;
953
+ await delay(LEGACY_SWEEP_WAIT_INTERVAL);
954
+ }
955
+ return "done";
956
+ }
957
+ #isLegacySweepDone() {
958
+ if (this.#legacySweepDone === "final") return true;
959
+ if (this.#legacySweepDone == null) return false;
960
+ if (Temporal.Instant.compare(this.#now(), this.#legacySweepDone) < 0) return true;
961
+ this.#legacySweepDone = void 0;
962
+ return false;
963
+ }
964
+ #rememberLegacySweepMarker(marker) {
965
+ const doneUntil = getLegacySweepDoneUntil(marker, this.#now());
966
+ if (doneUntil != null) this.#legacySweepDone = doneUntil;
967
+ }
862
968
  #capHeldRetryAt(now, heldSince, retryAt) {
863
969
  const expiresAt = (heldSince ?? now).add(this.#options.heldActivityTtl);
864
970
  return Temporal.Instant.compare(expiresAt, retryAt) < 0 ? expiresAt : retryAt;
865
971
  }
866
972
  async #get(remoteHost) {
867
- return parseCircuitBreakerKvState(await this.#kv.get(this.#key(remoteHost)));
973
+ return parseStoredCircuitBreakerKvState(await this.#kv.get(this.#key(remoteHost)));
868
974
  }
869
975
  async #replace(remoteHost, oldState, newState) {
870
976
  const key = this.#key(remoteHost);
977
+ const storedState = newState == null ? void 0 : markCircuitBreakerState(newState);
871
978
  if (this.#kv.cas == null) {
872
- if (newState == null) await this.#kv.delete(key);
873
- else await this.#kv.set(key, newState);
979
+ if (storedState == null) await this.#kv.delete(key);
980
+ else await this.#kv.set(key, storedState, this.#setOptions());
874
981
  return true;
875
982
  }
876
- return await this.#kv.cas(key, oldState, newState);
983
+ return await this.#kv.cas(key, oldState, storedState, storedState == null ? void 0 : this.#setOptions());
984
+ }
985
+ #setOptions() {
986
+ return this.#options.stateTtl == null ? void 0 : { ttl: this.#options.stateTtl };
877
987
  }
878
988
  async #notifyStateChange(remoteHost, previousState, newState) {
879
989
  try {
@@ -913,18 +1023,25 @@ var CircuitBreaker = class {
913
1023
  * @param options The public circuit breaker options supplied to Fedify.
914
1024
  * @returns The normalized failure predicate, failure pruning function,
915
1025
  * duration values, and optional callbacks with defaults applied.
916
- * @throws {RangeError} If any configured duration is not positive.
1026
+ * @throws {RangeError} If any configured duration is not positive, or if
1027
+ * `stateTtl` is shorter than `recoveryDelay`.
917
1028
  * @throws {TypeError} If `failureThreshold` is not a positive integer.
918
1029
  */
919
1030
  function normalizeCircuitBreakerOptions(options) {
920
1031
  const recoveryDelay = toInstantDuration(options.recoveryDelay ?? { minutes: 30 });
921
1032
  const heldActivityTtl = toInstantDuration(options.heldActivityTtl ?? { hours: 168 });
922
1033
  const releaseInterval = toInstantDuration(options.releaseInterval ?? { seconds: 1 });
1034
+ const configuredStateTtl = options.stateTtl == null ? void 0 : toInstantDuration(options.stateTtl);
923
1035
  assertPositiveDuration(recoveryDelay, "recoveryDelay");
924
1036
  assertPositiveDuration(heldActivityTtl, "heldActivityTtl");
925
1037
  assertPositiveDuration(releaseInterval, "releaseInterval");
1038
+ if (configuredStateTtl != null) {
1039
+ assertPositiveDuration(configuredStateTtl, "stateTtl");
1040
+ if (Temporal.Duration.compare(configuredStateTtl, recoveryDelay) < 0) throw new RangeError("stateTtl must be at least recoveryDelay.");
1041
+ }
926
1042
  let failure;
927
1043
  let pruneFailures;
1044
+ let stateTtl;
928
1045
  if (options.failure == null) {
929
1046
  const failureThreshold = options.failureThreshold ?? 5;
930
1047
  if (!Number.isInteger(failureThreshold) || failureThreshold <= 0) throw new TypeError("failureThreshold must be a positive integer.");
@@ -940,9 +1057,11 @@ function normalizeCircuitBreakerOptions(options) {
940
1057
  const last = timestamps[timestamps.length - 1];
941
1058
  return Temporal.Duration.compare(first.until(last), failureWindow) <= 0;
942
1059
  };
1060
+ stateTtl = configuredStateTtl ?? maxDuration(recoveryDelay.add(failureWindow), recoveryDelay.add(heldActivityTtl));
943
1061
  } else {
944
1062
  failure = options.failure;
945
1063
  pruneFailures = (timestamps) => timestamps.slice(-MAX_CUSTOM_FAILURE_HISTORY);
1064
+ stateTtl = configuredStateTtl;
946
1065
  }
947
1066
  return {
948
1067
  failure,
@@ -950,10 +1069,72 @@ function normalizeCircuitBreakerOptions(options) {
950
1069
  recoveryDelay,
951
1070
  heldActivityTtl,
952
1071
  releaseInterval,
1072
+ stateTtl,
953
1073
  onStateChange: options.onStateChange,
954
1074
  onActivityDrop: options.onActivityDrop
955
1075
  };
956
1076
  }
1077
+ function maxDuration(duration, ...durations) {
1078
+ return durations.reduce((max, candidate) => Temporal.Duration.compare(candidate, max) > 0 ? candidate : max, duration);
1079
+ }
1080
+ function isLegacySweepDone(value, now) {
1081
+ return getLegacySweepDoneUntil(value, now) != null;
1082
+ }
1083
+ function getLegacySweepDoneUntil(value, now) {
1084
+ if (typeof value !== "object" || value == null || !("state" in value)) return;
1085
+ if (value.state === "final") return "final";
1086
+ if (value.state === "done" && "retryUntil" in value && typeof value.retryUntil === "string") try {
1087
+ const retryUntil = Temporal.Instant.from(value.retryUntil);
1088
+ return Temporal.Instant.compare(now, retryUntil) < 0 ? retryUntil : void 0;
1089
+ } catch {
1090
+ return;
1091
+ }
1092
+ }
1093
+ function isLegacySweepRetryDue(value, now) {
1094
+ if (typeof value !== "object" || value == null || !("state" in value) || value.state !== "done" || !("retryUntil" in value) || typeof value.retryUntil !== "string") return false;
1095
+ try {
1096
+ return Temporal.Instant.compare(now, Temporal.Instant.from(value.retryUntil)) >= 0;
1097
+ } catch {
1098
+ return false;
1099
+ }
1100
+ }
1101
+ function isLegacySweepActive(value, now) {
1102
+ return isLegacySweepDone(value, now) || isLegacySweepInProgress(value, now);
1103
+ }
1104
+ function isLegacySweepInProgress(value, now) {
1105
+ if (typeof value !== "object" || value == null || !("state" in value) || value.state !== "sweeping" || !("started" in value) || typeof value.started !== "string") return false;
1106
+ try {
1107
+ return Temporal.Instant.compare(now, Temporal.Instant.from(value.started).add(LEGACY_SWEEP_LOCK_TTL)) < 0;
1108
+ } catch {
1109
+ return false;
1110
+ }
1111
+ }
1112
+ function getLegacySweepRetryUntil(value) {
1113
+ if (typeof value !== "object" || value == null || !("retryUntil" in value) || typeof value.retryUntil !== "string") return;
1114
+ try {
1115
+ Temporal.Instant.from(value.retryUntil);
1116
+ return value.retryUntil;
1117
+ } catch {
1118
+ return;
1119
+ }
1120
+ }
1121
+ function isCurrentCircuitBreakerState(value) {
1122
+ return typeof value === "object" && value != null && "__fedifyCircuitBreakerStateVersion" in value && value.__fedifyCircuitBreakerStateVersion === CIRCUIT_BREAKER_STATE_VERSION;
1123
+ }
1124
+ function markCircuitBreakerState(state) {
1125
+ return {
1126
+ ...state,
1127
+ __fedifyCircuitBreakerStateVersion: CIRCUIT_BREAKER_STATE_VERSION
1128
+ };
1129
+ }
1130
+ function stripStoredCircuitBreakerState(state) {
1131
+ if (state == null) return void 0;
1132
+ const { __fedifyCircuitBreakerStateVersion: _, ...publicState } = state;
1133
+ return publicState;
1134
+ }
1135
+ function delay(milliseconds) {
1136
+ return new Promise((resolve) => setTimeout(resolve, milliseconds));
1137
+ }
957
1138
  function toInstantDuration(duration) {
958
1139
  const parsed = Temporal.Duration.from(duration);
959
1140
  return Temporal.Duration.from({ milliseconds: Math.trunc(parsed.total({
@@ -988,6 +1169,9 @@ function parseHeldSince(value) {
988
1169
  * valid instant strings, or `undefined` when the stored value is malformed.
989
1170
  */
990
1171
  function parseCircuitBreakerKvState(value) {
1172
+ return stripStoredCircuitBreakerState(parseStoredCircuitBreakerKvState(value));
1173
+ }
1174
+ function parseStoredCircuitBreakerKvState(value) {
991
1175
  const isInstantString = (v) => {
992
1176
  if (typeof v !== "string") return false;
993
1177
  try {
@@ -1007,7 +1191,8 @@ function parseCircuitBreakerKvState(value) {
1007
1191
  state: record.state,
1008
1192
  failures: record.failures,
1009
1193
  ...record.opened == null ? {} : { opened: record.opened },
1010
- ...record.halfOpened == null ? {} : { halfOpened: record.halfOpened }
1194
+ ...record.halfOpened == null ? {} : { halfOpened: record.halfOpened },
1195
+ ...record.__fedifyCircuitBreakerStateVersion === CIRCUIT_BREAKER_STATE_VERSION ? { __fedifyCircuitBreakerStateVersion: CIRCUIT_BREAKER_STATE_VERSION } : {}
1011
1196
  };
1012
1197
  }
1013
1198
  //#endregion
@@ -3559,6 +3744,24 @@ const logger = (0, _logtape_logtape.getLogger)([
3559
3744
  "webfinger",
3560
3745
  "server"
3561
3746
  ]);
3747
+ function getWebFingerSubjectAndAliases({ resourceUrl, actorUri, preferredUsername, acctUsername, host, contextHost }) {
3748
+ const aliases = [];
3749
+ let subject = resourceUrl.href;
3750
+ if (resourceUrl.protocol != "acct:" && preferredUsername != null) {
3751
+ aliases.push(`acct:${preferredUsername}@${host ?? contextHost}`);
3752
+ if (host != null && host !== contextHost) aliases.push(`acct:${preferredUsername}@${contextHost}`);
3753
+ }
3754
+ if (resourceUrl.href !== actorUri.href) aliases.push(actorUri.href);
3755
+ if (resourceUrl.protocol === "acct:" && host != null && host !== contextHost && !resourceUrl.href.endsWith(`@${host}`)) {
3756
+ subject = `acct:${preferredUsername ?? acctUsername}@${host}`;
3757
+ aliases.push(resourceUrl.href);
3758
+ if (preferredUsername != null && preferredUsername !== "" && preferredUsername !== acctUsername) aliases.push(`acct:${preferredUsername}@${contextHost}`);
3759
+ }
3760
+ return {
3761
+ subject,
3762
+ aliases
3763
+ };
3764
+ }
3562
3765
  /**
3563
3766
  * Handles a WebFinger request. You would not typically call this function
3564
3767
  * directly, but instead use {@link Federation.fetch} method.
@@ -3663,6 +3866,7 @@ async function handleWebFingerInternal(request, { context, host, actorDispatcher
3663
3866
  return identifier;
3664
3867
  }
3665
3868
  let identifier = null;
3869
+ let acctUsername = null;
3666
3870
  const uriParsed = context.parseUri(resourceUrl);
3667
3871
  if (uriParsed?.type != "actor") {
3668
3872
  const match = /^acct:([^@]+)@([^@]+)$/.exec(resource);
@@ -3676,8 +3880,9 @@ async function handleWebFingerInternal(request, { context, host, actorDispatcher
3676
3880
  const normalizedHost = portMatch == null ? (0, node_url.domainToASCII)(match[2].toLowerCase()) : (0, node_url.domainToASCII)(match[2].substring(0, portMatch.index).toLowerCase()) + portMatch[0];
3677
3881
  if (normalizedHost != context.url.host && normalizedHost != host) return await onNotFound(request);
3678
3882
  else {
3679
- identifier = await mapUsernameToIdentifier(match[1]);
3680
- resourceUrl = new URL(`acct:${match[1]}@${normalizedHost}`);
3883
+ acctUsername = match[1];
3884
+ identifier = await mapUsernameToIdentifier(acctUsername);
3885
+ resourceUrl = new URL(`acct:${acctUsername}@${normalizedHost}`);
3681
3886
  }
3682
3887
  }
3683
3888
  } else identifier = uriParsed.identifier;
@@ -3691,9 +3896,10 @@ async function handleWebFingerInternal(request, { context, host, actorDispatcher
3691
3896
  status: 410,
3692
3897
  headers: { "Access-Control-Allow-Origin": "*" }
3693
3898
  });
3899
+ const actorUri = context.getActorUri(identifier);
3694
3900
  const links = [{
3695
3901
  rel: "self",
3696
- href: context.getActorUri(identifier).href,
3902
+ href: actorUri.href,
3697
3903
  type: "application/activity+json"
3698
3904
  }];
3699
3905
  for (const url of actor.urls) if (url instanceof _fedify_vocab.Link && url.href != null) links.push({
@@ -3717,23 +3923,19 @@ async function handleWebFingerInternal(request, { context, host, actorDispatcher
3717
3923
  const customLinks = await webFingerLinksDispatcher(context, resourceUrl);
3718
3924
  if (customLinks != null) for (const link of customLinks) links.push(link);
3719
3925
  }
3720
- const aliases = [];
3721
- const preferredUsername = actor.preferredUsername;
3722
- if (resourceUrl.protocol != "acct:" && preferredUsername != null) {
3723
- aliases.push(`acct:${preferredUsername}@${host ?? context.url.host}`);
3724
- if (host != null && host !== context.url.host) aliases.push(`acct:${preferredUsername}@${context.url.host}`);
3725
- }
3726
- if (resourceUrl.href !== context.getActorUri(identifier).href) aliases.push(context.getActorUri(identifier).href);
3727
- if (resourceUrl.protocol === "acct:" && host != null && host !== context.url.host && !resourceUrl.href.endsWith(`@${host}`)) {
3728
- const username = resourceUrl.href.replace(/^acct:/, "").replace(/@.*$/, "");
3729
- aliases.push(`acct:${username}@${host}`);
3730
- }
3731
- const jrd = {
3732
- subject: resourceUrl.href,
3926
+ const { subject, aliases } = getWebFingerSubjectAndAliases({
3927
+ resourceUrl,
3928
+ actorUri,
3929
+ preferredUsername: actor.preferredUsername,
3930
+ acctUsername,
3931
+ host,
3932
+ contextHost: context.url.host
3933
+ });
3934
+ return new Response(JSON.stringify({
3935
+ subject,
3733
3936
  aliases,
3734
3937
  links
3735
- };
3736
- return new Response(JSON.stringify(jrd), { headers: {
3938
+ }), { headers: {
3737
3939
  "Content-Type": "application/jrd+json",
3738
3940
  "Access-Control-Allow-Origin": "*"
3739
3941
  } });
@@ -1,5 +1,5 @@
1
1
  import "@js-temporal/polyfill";
2
2
  import "urlpattern-polyfill";
3
3
  globalThis.addEventListener = () => {};
4
- import { n as FederationImpl } from "./middleware-WU_5fbGS.mjs";
4
+ import { n as FederationImpl } from "./middleware-CubmqVku.mjs";
5
5
  export { FederationImpl };
@@ -1,5 +1,5 @@
1
1
  /// <reference lib="esnext.temporal" />
2
- import { G as ActorDispatcher, K as ActorHandleMapper, W as ActorAliasMapper, l as RequestContext, pt as WebFingerLinksDispatcher } from "./context-BBVLF7lx.cjs";
2
+ import { G as ActorDispatcher, K as ActorHandleMapper, W as ActorAliasMapper, l as RequestContext, pt as WebFingerLinksDispatcher } from "./context-Bjswu3eC.cjs";
3
3
  import { MeterProvider, Span, Tracer } from "@opentelemetry/api";
4
4
  import { RouterError, RouterOptions, RouterRouteResult } from "@fedify/uri-template";
5
5
 
@@ -1,5 +1,5 @@
1
1
  /// <reference lib="esnext.temporal" />
2
- import { G as ActorDispatcher, K as ActorHandleMapper, W as ActorAliasMapper, l as RequestContext, pt as WebFingerLinksDispatcher } from "./context-BU6jSQdo.js";
2
+ import { G as ActorDispatcher, K as ActorHandleMapper, W as ActorAliasMapper, l as RequestContext, pt as WebFingerLinksDispatcher } from "./context-COxc_O-c.js";
3
3
  import { RouterError, RouterOptions, RouterRouteResult } from "@fedify/uri-template";
4
4
  import { MeterProvider, Span, Tracer } from "@opentelemetry/api";
5
5
 
package/dist/mod.cjs CHANGED
@@ -4,11 +4,11 @@ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
4
4
  require("./chunk-DDcVe30Y.cjs");
5
5
  const require_transformers = require("./transformers-NeAONrAq.cjs");
6
6
  require("./compat/mod.cjs");
7
- const require_http = require("./http-BajqV0pH.cjs");
8
- const require_middleware = require("./middleware-BiXEAbn2.cjs");
9
- const require_proof = require("./proof-HzavUYxN.cjs");
10
- const require_types = require("./types-KC4QAoxe.cjs");
11
- const require_kv_cache = require("./kv-cache-CVG3m2_-.cjs");
7
+ const require_http = require("./http-CpdQD2ke.cjs");
8
+ const require_middleware = require("./middleware-D2F1jSer.cjs");
9
+ const require_proof = require("./proof-BdnNZ9OM.cjs");
10
+ const require_types = require("./types-m8u9L86X.cjs");
11
+ const require_kv_cache = require("./kv-cache-B8gm4cVB.cjs");
12
12
  const require_federation_mod = require("./federation/mod.cjs");
13
13
  require("./nodeinfo/mod.cjs");
14
14
  require("./runtime/mod.cjs");
package/dist/mod.d.cts CHANGED
@@ -2,11 +2,11 @@
2
2
  import { a as InboundService, c as OutboundService, d as Software, f as Usage, i as parseNodeInfo, l as Protocol, n as ParseNodeInfoOptions, o as JsonValue, p as nodeInfoToJson, r as getNodeInfo, s as NodeInfo, t as GetNodeInfoOptions, u as Services } from "./client-CAM_bQXx.cjs";
3
3
  import { C as exportJwk, D as importJwk, E as generateCryptoKeyPair, S as KeyCache, T as fetchKeyDetailed, _ as validateAcceptSignature, a as VerifyRequestDetailedResult, b as FetchKeyOptions, c as signRequest, d as AcceptSignatureMember, f as AcceptSignatureParameters, g as parseAcceptSignature, h as fulfillAcceptSignature, i as SignRequestOptions, l as verifyRequest, m as formatAcceptSignature, n as HttpMessageSignaturesSpecDeterminer, o as VerifyRequestFailureReason, p as FulfillAcceptSignatureResult, r as Rfc9421SignRequestOptions, s as VerifyRequestOptions, t as HttpMessageSignaturesSpec, u as verifyRequestDetailed, v as FetchKeyDetailedResult, w as fetchKey, x as FetchKeyResult, y as FetchKeyErrorResult } from "./http-VyDTd4G3.cjs";
4
4
  import { i as getKeyOwner, n as GetKeyOwnerOptions, r as doesActorOwnKey, t as DoesActorOwnKeyOptions } from "./owner-DEvZuyOE.cjs";
5
- import { $ as CustomCollectionCursor, A as Rfc6570Expression, At as parseCircuitBreakerKvState, B as createFederationBuilder, C as FederationStartQueueOptions, Ct as CircuitBreakerFailurePolicy, D as InboxListenerSetters, Dt as CircuitBreakerStateChange, E as InboxChallengePolicy, Et as CircuitBreakerState, F as CreateExponentialBackoffPolicyOptions, G as ActorDispatcher, H as respondWithObject, I as RetryContext, J as AuthorizePredicate, K as ActorHandleMapper, L as RetryPolicy, M as FederationOrigin, N as FederationQueueOptions, O as ObjectCallbackSetters, Ot as NormalizedCircuitBreakerOptions, P as createFederation, Q as CustomCollectionCounter, R as createExponentialBackoffPolicy, S as FederationOptions, St as CircuitBreakerCreateOptions, T as IdempotencyStrategy, Tt as CircuitBreakerOptions, U as respondWithObjectIfAcceptable, V as RespondWithObjectOptions, W as ActorAliasMapper, X as CollectionCursor, Y as CollectionCounter, Z as CollectionDispatcher, _ as Federatable, _t as buildCollectionSynchronizationHeader, a as GetSignedKeyOptions, at as ObjectDispatcher, b as FederationBuilder, bt as CircuitBreakerActivityDrop, c as ParseUriResult, ct as OutboxListenerErrorHandler, d as SendActivityOptions, dt as UnverifiedActivityHandler, et as CustomCollectionDispatcher, f as SendActivityOptionsForCollection, ft as UnverifiedActivityReason, g as CustomCollectionCallbackSetters, gt as PageItems, h as ConstructorWithTypeId, ht as SenderKeyPair, i as GetActorOptions, it as ObjectAuthorizePredicate, j as FederationKvPrefixes, jt as ActivityTransformer, k as OutboxListenerSetters, kt as normalizeCircuitBreakerOptions, l as RequestContext, lt as OutboxPermanentFailureHandler, m as CollectionCallbackSetters, mt as SendActivityError, n as Context, nt as InboxListener, o as InboxContext, ot as OutboxErrorHandler, p as ActorCallbackSetters, pt as WebFingerLinksDispatcher, q as ActorKeyPairsDispatcher, r as ForwardActivityOptions, rt as NodeInfoDispatcher, s as OutboxContext, st as OutboxListener, t as ActorKeyPair, tt as InboxErrorHandler, u as RouteActivityOptions, ut as SharedInboxKeyDispatcher, v as Federation, vt as digest, w as IdempotencyKeyCallback, wt as CircuitBreakerKvState, x as FederationFetchOptions, xt as CircuitBreakerBeforeSendDecision, y as FederationBenchmarkOptions, yt as CircuitBreaker, z as Message } from "./context-BBVLF7lx.cjs";
5
+ import { $ as CustomCollectionCursor, A as Rfc6570Expression, At as parseCircuitBreakerKvState, B as createFederationBuilder, C as FederationStartQueueOptions, Ct as CircuitBreakerFailurePolicy, D as InboxListenerSetters, Dt as CircuitBreakerStateChange, E as InboxChallengePolicy, Et as CircuitBreakerState, F as CreateExponentialBackoffPolicyOptions, G as ActorDispatcher, H as respondWithObject, I as RetryContext, J as AuthorizePredicate, K as ActorHandleMapper, L as RetryPolicy, M as FederationOrigin, N as FederationQueueOptions, O as ObjectCallbackSetters, Ot as NormalizedCircuitBreakerOptions, P as createFederation, Q as CustomCollectionCounter, R as createExponentialBackoffPolicy, S as FederationOptions, St as CircuitBreakerCreateOptions, T as IdempotencyStrategy, Tt as CircuitBreakerOptions, U as respondWithObjectIfAcceptable, V as RespondWithObjectOptions, W as ActorAliasMapper, X as CollectionCursor, Y as CollectionCounter, Z as CollectionDispatcher, _ as Federatable, _t as buildCollectionSynchronizationHeader, a as GetSignedKeyOptions, at as ObjectDispatcher, b as FederationBuilder, bt as CircuitBreakerActivityDrop, c as ParseUriResult, ct as OutboxListenerErrorHandler, d as SendActivityOptions, dt as UnverifiedActivityHandler, et as CustomCollectionDispatcher, f as SendActivityOptionsForCollection, ft as UnverifiedActivityReason, g as CustomCollectionCallbackSetters, gt as PageItems, h as ConstructorWithTypeId, ht as SenderKeyPair, i as GetActorOptions, it as ObjectAuthorizePredicate, j as FederationKvPrefixes, jt as ActivityTransformer, k as OutboxListenerSetters, kt as normalizeCircuitBreakerOptions, l as RequestContext, lt as OutboxPermanentFailureHandler, m as CollectionCallbackSetters, mt as SendActivityError, n as Context, nt as InboxListener, o as InboxContext, ot as OutboxErrorHandler, p as ActorCallbackSetters, pt as WebFingerLinksDispatcher, q as ActorKeyPairsDispatcher, r as ForwardActivityOptions, rt as NodeInfoDispatcher, s as OutboxContext, st as OutboxListener, t as ActorKeyPair, tt as InboxErrorHandler, u as RouteActivityOptions, ut as SharedInboxKeyDispatcher, v as Federation, vt as digest, w as IdempotencyKeyCallback, wt as CircuitBreakerKvState, x as FederationFetchOptions, xt as CircuitBreakerBeforeSendDecision, y as FederationBenchmarkOptions, yt as CircuitBreaker, z as Message } from "./context-Bjswu3eC.cjs";
6
6
  import { a as MemoryKvStore, i as KvStoreSetOptions, n as KvStore, r as KvStoreListEntry, t as KvKey } from "./kv-gJ8LYbxX.cjs";
7
7
  import { a as MessageQueueEnqueueOptions, i as MessageQueueDepth, n as InProcessMessageQueueOptions, o as MessageQueueListenOptions, r as MessageQueue, s as ParallelMessageQueue, t as InProcessMessageQueue } from "./mq-D8uSFzxe.cjs";
8
8
  import { actorDehydrator, autoIdAssigner, getDefaultActivityTransformers } from "./compat/mod.cjs";
9
- import { a as RouterOptions, i as RouterError, n as handleWebFinger, o as RouterRouteResult, r as Router, t as WebFingerHandlerParameters } from "./mod-C0F6kvgS.cjs";
9
+ import { a as RouterOptions, i as RouterError, n as handleWebFinger, o as RouterRouteResult, r as Router, t as WebFingerHandlerParameters } from "./mod-0bIqGrKQ.cjs";
10
10
  import { _ as hasSignatureLike, a as createProof, b as verifySignature, c as verifyObject, d as SignJsonLdOptions, f as VerifyJsonLdOptions, g as detachSignature, h as createSignature, i as VerifyProofOptions, l as verifyProof, m as attachSignature, n as SignObjectOptions, o as hasProofLike, p as VerifySignatureOptions, r as VerifyObjectOptions, s as signObject, t as CreateProofOptions, u as CreateSignatureOptions, v as signJsonLd, y as verifyJsonLd } from "./mod-B0hW12_O.cjs";
11
11
  import { n as getAuthenticatedDocumentLoader, t as kvCache } from "./mod-yvIXFAEi.cjs";
12
12
  export * from "@fedify/vocab-runtime";
package/dist/mod.d.ts CHANGED
@@ -2,11 +2,11 @@
2
2
  import { a as InboundService, c as OutboundService, d as Software, f as Usage, i as parseNodeInfo, l as Protocol, n as ParseNodeInfoOptions, o as JsonValue, p as nodeInfoToJson, r as getNodeInfo, s as NodeInfo, t as GetNodeInfoOptions, u as Services } from "./client-CSddvgWN.js";
3
3
  import { C as exportJwk, D as importJwk, E as generateCryptoKeyPair, S as KeyCache, T as fetchKeyDetailed, _ as validateAcceptSignature, a as VerifyRequestDetailedResult, b as FetchKeyOptions, c as signRequest, d as AcceptSignatureMember, f as AcceptSignatureParameters, g as parseAcceptSignature, h as fulfillAcceptSignature, i as SignRequestOptions, l as verifyRequest, m as formatAcceptSignature, n as HttpMessageSignaturesSpecDeterminer, o as VerifyRequestFailureReason, p as FulfillAcceptSignatureResult, r as Rfc9421SignRequestOptions, s as VerifyRequestOptions, t as HttpMessageSignaturesSpec, u as verifyRequestDetailed, v as FetchKeyDetailedResult, w as fetchKey, x as FetchKeyResult, y as FetchKeyErrorResult } from "./http-lf8Hsd91.js";
4
4
  import { i as getKeyOwner, n as GetKeyOwnerOptions, r as doesActorOwnKey, t as DoesActorOwnKeyOptions } from "./owner-CnngXDNJ.js";
5
- import { $ as CustomCollectionCursor, A as Rfc6570Expression, At as parseCircuitBreakerKvState, B as createFederationBuilder, C as FederationStartQueueOptions, Ct as CircuitBreakerFailurePolicy, D as InboxListenerSetters, Dt as CircuitBreakerStateChange, E as InboxChallengePolicy, Et as CircuitBreakerState, F as CreateExponentialBackoffPolicyOptions, G as ActorDispatcher, H as respondWithObject, I as RetryContext, J as AuthorizePredicate, K as ActorHandleMapper, L as RetryPolicy, M as FederationOrigin, N as FederationQueueOptions, O as ObjectCallbackSetters, Ot as NormalizedCircuitBreakerOptions, P as createFederation, Q as CustomCollectionCounter, R as createExponentialBackoffPolicy, S as FederationOptions, St as CircuitBreakerCreateOptions, T as IdempotencyStrategy, Tt as CircuitBreakerOptions, U as respondWithObjectIfAcceptable, V as RespondWithObjectOptions, W as ActorAliasMapper, X as CollectionCursor, Y as CollectionCounter, Z as CollectionDispatcher, _ as Federatable, _t as buildCollectionSynchronizationHeader, a as GetSignedKeyOptions, at as ObjectDispatcher, b as FederationBuilder, bt as CircuitBreakerActivityDrop, c as ParseUriResult, ct as OutboxListenerErrorHandler, d as SendActivityOptions, dt as UnverifiedActivityHandler, et as CustomCollectionDispatcher, f as SendActivityOptionsForCollection, ft as UnverifiedActivityReason, g as CustomCollectionCallbackSetters, gt as PageItems, h as ConstructorWithTypeId, ht as SenderKeyPair, i as GetActorOptions, it as ObjectAuthorizePredicate, j as FederationKvPrefixes, jt as ActivityTransformer, k as OutboxListenerSetters, kt as normalizeCircuitBreakerOptions, l as RequestContext, lt as OutboxPermanentFailureHandler, m as CollectionCallbackSetters, mt as SendActivityError, n as Context, nt as InboxListener, o as InboxContext, ot as OutboxErrorHandler, p as ActorCallbackSetters, pt as WebFingerLinksDispatcher, q as ActorKeyPairsDispatcher, r as ForwardActivityOptions, rt as NodeInfoDispatcher, s as OutboxContext, st as OutboxListener, t as ActorKeyPair, tt as InboxErrorHandler, u as RouteActivityOptions, ut as SharedInboxKeyDispatcher, v as Federation, vt as digest, w as IdempotencyKeyCallback, wt as CircuitBreakerKvState, x as FederationFetchOptions, xt as CircuitBreakerBeforeSendDecision, y as FederationBenchmarkOptions, yt as CircuitBreaker, z as Message } from "./context-BU6jSQdo.js";
5
+ import { $ as CustomCollectionCursor, A as Rfc6570Expression, At as parseCircuitBreakerKvState, B as createFederationBuilder, C as FederationStartQueueOptions, Ct as CircuitBreakerFailurePolicy, D as InboxListenerSetters, Dt as CircuitBreakerStateChange, E as InboxChallengePolicy, Et as CircuitBreakerState, F as CreateExponentialBackoffPolicyOptions, G as ActorDispatcher, H as respondWithObject, I as RetryContext, J as AuthorizePredicate, K as ActorHandleMapper, L as RetryPolicy, M as FederationOrigin, N as FederationQueueOptions, O as ObjectCallbackSetters, Ot as NormalizedCircuitBreakerOptions, P as createFederation, Q as CustomCollectionCounter, R as createExponentialBackoffPolicy, S as FederationOptions, St as CircuitBreakerCreateOptions, T as IdempotencyStrategy, Tt as CircuitBreakerOptions, U as respondWithObjectIfAcceptable, V as RespondWithObjectOptions, W as ActorAliasMapper, X as CollectionCursor, Y as CollectionCounter, Z as CollectionDispatcher, _ as Federatable, _t as buildCollectionSynchronizationHeader, a as GetSignedKeyOptions, at as ObjectDispatcher, b as FederationBuilder, bt as CircuitBreakerActivityDrop, c as ParseUriResult, ct as OutboxListenerErrorHandler, d as SendActivityOptions, dt as UnverifiedActivityHandler, et as CustomCollectionDispatcher, f as SendActivityOptionsForCollection, ft as UnverifiedActivityReason, g as CustomCollectionCallbackSetters, gt as PageItems, h as ConstructorWithTypeId, ht as SenderKeyPair, i as GetActorOptions, it as ObjectAuthorizePredicate, j as FederationKvPrefixes, jt as ActivityTransformer, k as OutboxListenerSetters, kt as normalizeCircuitBreakerOptions, l as RequestContext, lt as OutboxPermanentFailureHandler, m as CollectionCallbackSetters, mt as SendActivityError, n as Context, nt as InboxListener, o as InboxContext, ot as OutboxErrorHandler, p as ActorCallbackSetters, pt as WebFingerLinksDispatcher, q as ActorKeyPairsDispatcher, r as ForwardActivityOptions, rt as NodeInfoDispatcher, s as OutboxContext, st as OutboxListener, t as ActorKeyPair, tt as InboxErrorHandler, u as RouteActivityOptions, ut as SharedInboxKeyDispatcher, v as Federation, vt as digest, w as IdempotencyKeyCallback, wt as CircuitBreakerKvState, x as FederationFetchOptions, xt as CircuitBreakerBeforeSendDecision, y as FederationBenchmarkOptions, yt as CircuitBreaker, z as Message } from "./context-COxc_O-c.js";
6
6
  import { a as MemoryKvStore, i as KvStoreSetOptions, n as KvStore, r as KvStoreListEntry, t as KvKey } from "./kv-D6hNiMTK.js";
7
7
  import { a as MessageQueueEnqueueOptions, i as MessageQueueDepth, n as InProcessMessageQueueOptions, o as MessageQueueListenOptions, r as MessageQueue, s as ParallelMessageQueue, t as InProcessMessageQueue } from "./mq-D-nlpY04.js";
8
8
  import { actorDehydrator, autoIdAssigner, getDefaultActivityTransformers } from "./compat/mod.js";
9
- import { a as RouterOptions, i as RouterError, n as handleWebFinger, o as RouterRouteResult, r as Router, t as WebFingerHandlerParameters } from "./mod-vPYVoa5n.js";
9
+ import { a as RouterOptions, i as RouterError, n as handleWebFinger, o as RouterRouteResult, r as Router, t as WebFingerHandlerParameters } from "./mod-3H9Gym5G.js";
10
10
  import { _ as hasSignatureLike, a as createProof, b as verifySignature, c as verifyObject, d as SignJsonLdOptions, f as VerifyJsonLdOptions, g as detachSignature, h as createSignature, i as VerifyProofOptions, l as verifyProof, m as attachSignature, n as SignObjectOptions, o as hasProofLike, p as VerifySignatureOptions, r as VerifyObjectOptions, s as signObject, t as CreateProofOptions, u as CreateSignatureOptions, v as signJsonLd, y as verifyJsonLd } from "./mod-COIAjwRS.js";
11
11
  import { n as getAuthenticatedDocumentLoader, t as kvCache } from "./mod-DFvNJcNb.js";
12
12
  export * from "@fedify/vocab-runtime";
package/dist/mod.js CHANGED
@@ -3,11 +3,11 @@ import { URLPattern } from "urlpattern-polyfill";
3
3
  import "./chunk-CRNNMoPX.js";
4
4
  import { n as autoIdAssigner, r as getDefaultActivityTransformers, t as actorDehydrator } from "./transformers-BGMIq1cs.js";
5
5
  import "./compat/mod.js";
6
- import { A as formatAcceptSignature, M as parseAcceptSignature, N as validateAcceptSignature, a as verifyRequestDetailed, c as fetchKeyDetailed, i as verifyRequest, j as fulfillAcceptSignature, l as generateCryptoKeyPair, o as exportJwk, r as signRequest, s as fetchKey, u as importJwk } from "./http-DPOMa1DD.js";
7
- import { a as SendActivityError, c as buildCollectionSynchronizationHeader, d as normalizeCircuitBreakerOptions, f as parseCircuitBreakerKvState, i as createExponentialBackoffPolicy, l as digest, o as respondWithObject, p as createFederationBuilder, r as handleWebFinger, s as respondWithObjectIfAcceptable, t as createFederation, u as CircuitBreaker } from "./middleware-By8lqc18.js";
8
- import { C as verifySignature, S as verifyJsonLd, _ as hasSignatureLike, a as verifyProof, b as signJsonLd, c as getKeyOwner, d as attachSignature, i as verifyObject, m as detachSignature, n as hasProofLike, p as createSignature, r as signObject, s as doesActorOwnKey, t as createProof } from "./proof-DA8Kbchf.js";
9
- import { n as getNodeInfo, r as parseNodeInfo, t as nodeInfoToJson } from "./types-CAY3OdLq.js";
10
- import { n as getAuthenticatedDocumentLoader, t as kvCache } from "./kv-cache-IjRceERu.js";
6
+ import { A as formatAcceptSignature, M as parseAcceptSignature, N as validateAcceptSignature, a as verifyRequestDetailed, c as fetchKeyDetailed, i as verifyRequest, j as fulfillAcceptSignature, l as generateCryptoKeyPair, o as exportJwk, r as signRequest, s as fetchKey, u as importJwk } from "./http-DVZxcZEr.js";
7
+ import { a as SendActivityError, c as buildCollectionSynchronizationHeader, d as normalizeCircuitBreakerOptions, f as parseCircuitBreakerKvState, i as createExponentialBackoffPolicy, l as digest, o as respondWithObject, p as createFederationBuilder, r as handleWebFinger, s as respondWithObjectIfAcceptable, t as createFederation, u as CircuitBreaker } from "./middleware-B2rDMvhv.js";
8
+ import { C as verifySignature, S as verifyJsonLd, _ as hasSignatureLike, a as verifyProof, b as signJsonLd, c as getKeyOwner, d as attachSignature, i as verifyObject, m as detachSignature, n as hasProofLike, p as createSignature, r as signObject, s as doesActorOwnKey, t as createProof } from "./proof-C_kcg7vF.js";
9
+ import { n as getNodeInfo, r as parseNodeInfo, t as nodeInfoToJson } from "./types-DTONwNqL.js";
10
+ import { n as getAuthenticatedDocumentLoader, t as kvCache } from "./kv-cache-B52kTPj4.js";
11
11
  import { InProcessMessageQueue, MemoryKvStore, ParallelMessageQueue, Router, RouterError } from "./federation/mod.js";
12
12
  import "./nodeinfo/mod.js";
13
13
  import "./runtime/mod.js";
@@ -4,7 +4,7 @@ globalThis.addEventListener = () => {};
4
4
  import { t as esm_default } from "../esm-vrlUxr60.mjs";
5
5
  import { t as assertEquals } from "../assert_equals-C-ZRDbaf.mjs";
6
6
  import "../std__assert-BBjXFNOb.mjs";
7
- import { a as parseProtocol, c as parseUsage, i as parseOutboundService, n as parseInboundService, o as parseServices, r as parseNodeInfo, s as parseSoftware, t as getNodeInfo } from "../client-ByXmQhYD.mjs";
7
+ import { a as parseProtocol, c as parseUsage, i as parseOutboundService, n as parseInboundService, o as parseServices, r as parseNodeInfo, s as parseSoftware, t as getNodeInfo } from "../client-D1KuWxjH.mjs";
8
8
  import { test } from "@fedify/fixture";
9
9
  //#region src/nodeinfo/client.test.ts
10
10
  test("getNodeInfo()", async (t) => {
@@ -62,6 +62,66 @@ test("getNodeInfo()", async (t) => {
62
62
  });
63
63
  esm_default.hardReset();
64
64
  });
65
+ test("getNodeInfo() blocks SSRF", async (t) => {
66
+ esm_default.spyGlobal();
67
+ await t.step("link href pointing to a loopback address", async () => {
68
+ esm_default.removeRoutes();
69
+ esm_default.get("https://example.com/.well-known/nodeinfo", { body: { links: [{
70
+ rel: "http://nodeinfo.diaspora.software/ns/schema/2.1",
71
+ href: "http://127.0.0.1:8080/internal"
72
+ }] } });
73
+ let internalFetched = false;
74
+ esm_default.get("http://127.0.0.1:8080/internal", () => {
75
+ internalFetched = true;
76
+ return { body: { secret: "INTERNAL_DATA_LEAKED" } };
77
+ });
78
+ assertEquals(await getNodeInfo("https://example.com/", { parse: "none" }), void 0);
79
+ assertEquals(internalFetched, false);
80
+ });
81
+ await t.step("link href using a data: URL", async () => {
82
+ esm_default.removeRoutes();
83
+ esm_default.get("https://example.com/.well-known/nodeinfo", { body: { links: [{
84
+ rel: "http://nodeinfo.diaspora.software/ns/schema/2.0",
85
+ href: "data:application/json,{\"software\":{\"name\":\"x\"}}"
86
+ }] } });
87
+ assertEquals(await getNodeInfo("https://example.com/", { parse: "none" }), void 0);
88
+ });
89
+ await t.step("redirect to a private address", async () => {
90
+ esm_default.removeRoutes();
91
+ esm_default.get("https://example.com/.well-known/nodeinfo", { body: { links: [{
92
+ rel: "http://nodeinfo.diaspora.software/ns/schema/2.1",
93
+ href: "https://example.com/nodeinfo/2.1"
94
+ }] } });
95
+ esm_default.get("https://example.com/nodeinfo/2.1", {
96
+ status: 302,
97
+ headers: { Location: "https://127.0.0.1:8080/internal" }
98
+ });
99
+ let internalFetched = false;
100
+ esm_default.get("https://127.0.0.1:8080/internal", () => {
101
+ internalFetched = true;
102
+ return { body: { secret: "INTERNAL_DATA_LEAKED" } };
103
+ });
104
+ assertEquals(await getNodeInfo("https://example.com/", { parse: "none" }), void 0);
105
+ assertEquals(internalFetched, false);
106
+ });
107
+ await t.step("direct fetch of a private address", async () => {
108
+ esm_default.removeRoutes();
109
+ let internalFetched = false;
110
+ esm_default.get("http://127.0.0.1:8080/nodeinfo", () => {
111
+ internalFetched = true;
112
+ return { body: { software: {
113
+ name: "x",
114
+ version: "1.0.0"
115
+ } } };
116
+ });
117
+ assertEquals(await getNodeInfo("http://127.0.0.1:8080/nodeinfo", {
118
+ direct: true,
119
+ parse: "none"
120
+ }), void 0);
121
+ assertEquals(internalFetched, false);
122
+ });
123
+ esm_default.hardReset();
124
+ });
65
125
  test("parseNodeInfo()", () => {
66
126
  const input = {
67
127
  software: {
@@ -5,7 +5,7 @@ import { r as createRequestContext } from "../context-DVoTs_wM.mjs";
5
5
  import { t as assertEquals } from "../assert_equals-C-ZRDbaf.mjs";
6
6
  import "../std__assert-BBjXFNOb.mjs";
7
7
  import { t as MemoryKvStore } from "../kv-rV3vodCc.mjs";
8
- import { _ as handleNodeInfo, o as createFederation, v as handleNodeInfoJrd } from "../middleware-WU_5fbGS.mjs";
8
+ import { _ as handleNodeInfo, o as createFederation, v as handleNodeInfoJrd } from "../middleware-CubmqVku.mjs";
9
9
  import { test } from "@fedify/fixture";
10
10
  //#region src/nodeinfo/handler.test.ts
11
11
  test("handleNodeInfo()", async () => {
@@ -1,7 +1,7 @@
1
1
  const { Temporal } = require("@js-temporal/polyfill");
2
2
  const { URLPattern } = require("urlpattern-polyfill");
3
3
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
4
- const require_types = require("../types-KC4QAoxe.cjs");
4
+ const require_types = require("../types-m8u9L86X.cjs");
5
5
  exports.getNodeInfo = require_types.getNodeInfo;
6
6
  exports.nodeInfoToJson = require_types.nodeInfoToJson;
7
7
  exports.parseNodeInfo = require_types.parseNodeInfo;
@@ -1,4 +1,4 @@
1
1
  import { Temporal } from "@js-temporal/polyfill";
2
2
  import { URLPattern } from "urlpattern-polyfill";
3
- import { n as getNodeInfo, r as parseNodeInfo, t as nodeInfoToJson } from "../types-CAY3OdLq.js";
3
+ import { n as getNodeInfo, r as parseNodeInfo, t as nodeInfoToJson } from "../types-DTONwNqL.js";
4
4
  export { getNodeInfo, nodeInfoToJson, parseNodeInfo };
@@ -1,8 +1,8 @@
1
1
  import "@js-temporal/polyfill";
2
2
  import "urlpattern-polyfill";
3
3
  globalThis.addEventListener = () => {};
4
- import { n as version, t as name } from "./deno-C8RG0HM3.mjs";
5
- import "./key-BKYwmbur.mjs";
4
+ import { n as version, t as name } from "./deno-B8lcQ4Ms.mjs";
5
+ import "./key-BqT9YrM3.mjs";
6
6
  import { CryptographicKey, Object as Object$1, isActor } from "@fedify/vocab";
7
7
  import { SpanKind, SpanStatusCode, trace } from "@opentelemetry/api";
8
8
  import { getDocumentLoader } from "@fedify/vocab-runtime";