@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.
Files changed (244) hide show
  1. package/README.md +50 -39
  2. package/dist/{assert-CJ1KXcvv.mjs → assert-D1Eafmd_.mjs} +1 -1
  3. package/dist/{assert_equals-BV7oLMxm.mjs → assert_equals-Z6IcEbWU.mjs} +2 -28
  4. package/dist/assert_exists-cffSpJ7X.mjs +28 -0
  5. package/dist/assert_false-CPdRnc3E.mjs +23 -0
  6. package/dist/assert_greater_or_equal-BlafOF4-.mjs +31 -0
  7. package/dist/{assert_instance_of-Q9KTBnrf.mjs → assert_instance_of-mrXWWNY-.mjs} +1 -1
  8. package/dist/{assert_not_equals-EPJPoxuf.mjs → assert_not_equals-CpPK2t89.mjs} +2 -1
  9. package/dist/assert_rejects-CTptdYck.mjs +39 -0
  10. package/dist/{assert_strict_equals-Ec20AUwa.mjs → assert_strict_equals-CYvBmkpC.mjs} +2 -1
  11. package/dist/{assert_throws-NiFfqbnH.mjs → assert_throws-BYITOvPO.mjs} +2 -1
  12. package/dist/assertion_error-B-cx0fFA.mjs +31 -0
  13. package/dist/audience-CHQ-FlLD.mjs +95 -0
  14. package/dist/{builder-DonuxRl_.mjs → builder-Bda6LK7A.mjs} +169 -14
  15. package/dist/{circuit-breaker-CBGyz5F7.mjs → circuit-breaker-XJ53md4S.mjs} +224 -17
  16. package/dist/circuit-breaker-test-utils-DW6YC_Pe.mjs +14 -0
  17. package/dist/{client-CjF5IYQS.mjs → client-CEnNz8O7.mjs} +144 -8
  18. package/dist/{codec-BBga-FWI.mjs → codec-C6rTrpIp.mjs} +26 -3
  19. package/dist/{collection-BIhlldqK.mjs → collection-CnnP1UAv.mjs} +1 -1
  20. package/dist/compat/mod.cjs +1 -1
  21. package/dist/compat/mod.d.cts +1 -1
  22. package/dist/compat/mod.d.ts +1 -1
  23. package/dist/compat/mod.js +1 -1
  24. package/dist/compat/outgoing-jsonld.test.mjs +71 -3
  25. package/dist/compat/public-audience.test.mjs +14 -5
  26. package/dist/compat/secured-document.test.d.mts +2 -0
  27. package/dist/compat/secured-document.test.mjs +58 -0
  28. package/dist/compat/transformers.test.mjs +5 -5
  29. package/dist/compound-proof-B-9mXxFW.mjs +989 -0
  30. package/dist/{context-Zii1f5xS.d.ts → context-7NyiqIcg.d.ts} +1600 -25
  31. package/dist/{context-BJXxPElm.mjs → context-8Tu9kea0.mjs} +21 -63
  32. package/dist/{context-lxX6I7as.d.cts → context-tsYPJ2Ew.d.cts} +1600 -25
  33. package/dist/{deno-BHzKn9Ig.mjs → deno-oR5aJKpU.mjs} +1 -1
  34. package/dist/dist-DJQM4u7i.mjs +189 -0
  35. package/dist/docloader-DBoYbvYW.mjs +87 -0
  36. package/dist/{esm-yYQfPhIY.mjs → esm-BHXXx4Wx.mjs} +13 -6
  37. package/dist/federation/audience.test.d.mts +2 -0
  38. package/dist/federation/audience.test.mjs +130 -0
  39. package/dist/federation/builder.test.mjs +54 -5
  40. package/dist/federation/circuit-breaker.test.mjs +838 -7
  41. package/dist/federation/collection.test.mjs +3 -3
  42. package/dist/federation/compound-fanout.test.d.mts +2 -0
  43. package/dist/federation/compound-fanout.test.mjs +228 -0
  44. package/dist/federation/handler-compound-proof.test.d.mts +2 -0
  45. package/dist/federation/handler-compound-proof.test.mjs +384 -0
  46. package/dist/federation/handler.test.mjs +385 -15
  47. package/dist/federation/hashlink-media.test.d.mts +2 -0
  48. package/dist/federation/hashlink-media.test.mjs +422 -0
  49. package/dist/federation/idempotency.test.mjs +6 -6
  50. package/dist/federation/inbox-report.test.d.mts +2 -0
  51. package/dist/federation/inbox-report.test.mjs +475 -0
  52. package/dist/federation/inbox.test.mjs +3 -3
  53. package/dist/federation/keycache.test.mjs +215 -14
  54. package/dist/federation/kv.test.mjs +3 -3
  55. package/dist/federation/metrics.test.mjs +5 -5
  56. package/dist/federation/middleware.test.mjs +967 -35
  57. package/dist/federation/mod.cjs +18 -261
  58. package/dist/federation/mod.d.cts +5 -5
  59. package/dist/federation/mod.d.ts +5 -5
  60. package/dist/federation/mod.js +279 -1
  61. package/dist/federation/mq.test.mjs +123 -47
  62. package/dist/federation/negotiation.test.mjs +4 -4
  63. package/dist/federation/outgoing-proof.test.d.mts +2 -0
  64. package/dist/federation/outgoing-proof.test.mjs +931 -0
  65. package/dist/federation/portable-actor.test.d.mts +2 -0
  66. package/dist/federation/portable-actor.test.mjs +496 -0
  67. package/dist/federation/portable-chain.test.d.mts +2 -0
  68. package/dist/federation/portable-chain.test.mjs +370 -0
  69. package/dist/federation/portable-collection.test.d.mts +2 -0
  70. package/dist/federation/portable-collection.test.mjs +832 -0
  71. package/dist/federation/portable-delivery.test.d.mts +2 -0
  72. package/dist/federation/portable-delivery.test.mjs +746 -0
  73. package/dist/federation/portable-gateway-keys.test.d.mts +2 -0
  74. package/dist/federation/portable-gateway-keys.test.mjs +686 -0
  75. package/dist/federation/portable-inbox.test.d.mts +2 -0
  76. package/dist/federation/portable-inbox.test.mjs +1535 -0
  77. package/dist/federation/portable-parse-uri.test.d.mts +2 -0
  78. package/dist/federation/portable-parse-uri.test.mjs +348 -0
  79. package/dist/federation/portable-route-activity.test.d.mts +2 -0
  80. package/dist/federation/portable-route-activity.test.mjs +259 -0
  81. package/dist/federation/portable.test.d.mts +2 -0
  82. package/dist/federation/portable.test.mjs +1072 -0
  83. package/dist/federation/retry.test.mjs +4 -3
  84. package/dist/federation/router.test.mjs +9 -159
  85. package/dist/federation/send.test.mjs +245 -27
  86. package/dist/federation/tasks/codec.test.mjs +4 -4
  87. package/dist/federation/tasks/enqueue.test.mjs +42 -4
  88. package/dist/federation/tasks/tasks.test.mjs +7 -6
  89. package/dist/federation/temporal.test.mjs +4 -4
  90. package/dist/federation/webfinger.test.mjs +275 -12
  91. package/dist/{middleware-FrSMAYEx.cjs → federation-CkZiAcWY.cjs} +8640 -3722
  92. package/dist/http-B5JT115n.cjs +10630 -0
  93. package/dist/http-DydWCB7R.js +6527 -0
  94. package/dist/{http-CPVMUgsW.mjs → http-siciWkmx.mjs} +397 -168
  95. package/dist/key-BspPiXqn.mjs +951 -0
  96. package/dist/key-C1nv7_jD.d.ts +1125 -0
  97. package/dist/key-DYxuyd-s.d.cts +1125 -0
  98. package/dist/key-owner-evidence-APB6NpSk.mjs +108 -0
  99. package/dist/keycache-Da8A8mZa.mjs +200 -0
  100. package/dist/{kv-D6hNiMTK.d.ts → kv-B2mzK5tl.d.cts} +3 -2
  101. package/dist/{kv-gJ8LYbxX.d.cts → kv-B2mzK5tl.d.ts} +3 -2
  102. package/dist/kv-DBRWVx3Q.mjs +104 -0
  103. package/dist/{kv-cache-KyGqJqSp.mjs → kv-cache-BF1hnW65.mjs} +1 -1
  104. package/dist/{kv-cache-5bnX3-Jq.js → kv-cache-CTQr1tbn.js} +54 -16
  105. package/dist/{kv-cache-DQ29C4XL.cjs → kv-cache-DCac1nEU.cjs} +55 -19
  106. package/dist/{ld-Cj9F_5AP.mjs → ld-CzaP-hJl.mjs} +44 -12
  107. package/dist/map-local-context-conflict-DOYEEJ3u.mjs +204 -0
  108. package/dist/map-local-create-note-B0SeXJms.mjs +214 -0
  109. package/dist/{metrics-DMk-nTxI.mjs → metrics-CYg9yxBL.mjs} +3 -2
  110. package/dist/metrics-DVKYY6K1.d.cts +36 -0
  111. package/dist/metrics-DoRtI0qU.d.ts +36 -0
  112. package/dist/{middleware-Y-kpsp6S.js → middleware-CsDDXKuL.js} +5493 -1103
  113. package/dist/{middleware-CvwTUgnP.mjs → middleware-D7_LO-Vs.mjs} +3157 -272
  114. package/dist/{middleware-CEctxBN6.mjs → middleware-DWjrfpcz.mjs} +1 -1
  115. package/dist/{mod-gOlPDuT8.d.cts → mod-BojP2Bw3.d.cts} +1 -1
  116. package/dist/mod-CavhAgLw.d.cts +320 -0
  117. package/dist/mod-CtpF2BU5.d.ts +320 -0
  118. package/dist/{mod-BtEaIEcD.d.cts → mod-DEZXwV6J.d.ts} +10 -4
  119. package/dist/{mod-UMmveiO0.d.ts → mod-Dw-nPxQh.d.cts} +10 -4
  120. package/dist/{mod-DLc2Ot6f.d.ts → mod-Xj5P4s4d.d.ts} +1 -1
  121. package/dist/mod.cjs +40 -39
  122. package/dist/mod.d.cts +10 -11
  123. package/dist/mod.d.ts +10 -11
  124. package/dist/mod.js +9 -9
  125. package/dist/{mq-llHKivST.mjs → mq-Dm1Wmtm-.mjs} +6 -0
  126. package/dist/nodeinfo/client.test.mjs +139 -4
  127. package/dist/nodeinfo/handler.test.mjs +5 -5
  128. package/dist/nodeinfo/mod.cjs +1 -1
  129. package/dist/nodeinfo/mod.d.cts +1 -1
  130. package/dist/nodeinfo/mod.d.ts +1 -1
  131. package/dist/nodeinfo/mod.js +1 -1
  132. package/dist/nodeinfo/types.test.mjs +4 -4
  133. package/dist/otel/exporter.test.mjs +3 -3
  134. package/dist/otel/mod.cjs +0 -1
  135. package/dist/otel/mod.d.cts +1 -1
  136. package/dist/otel/mod.d.ts +1 -1
  137. package/dist/{outgoing-jsonld-URuqsICz.mjs → outgoing-jsonld-4IJqPYos.mjs} +12 -10
  138. package/dist/{owner-DEvZuyOE.d.cts → owner-BF0L9cMt.d.cts} +11 -0
  139. package/dist/{owner-CnngXDNJ.d.ts → owner-BSzr5-I8.d.ts} +11 -0
  140. package/dist/owner-DYgfTFHy.mjs +267 -0
  141. package/dist/portable-collection-BpsVsRov.js +670 -0
  142. package/dist/portable-collection-C74P1vtC.mjs +418 -0
  143. package/dist/portable-collection-DZd2bm7M.cjs +686 -0
  144. package/dist/portable-key-BIMLrAP4.mjs +549 -0
  145. package/dist/portable-key-id-Ca30-hOa.mjs +248 -0
  146. package/dist/proof-Dxy6ByqI.mjs +1299 -0
  147. package/dist/{public-audience-Cr5MsnMq.mjs → public-audience--klBIOk1.mjs} +15 -10
  148. package/dist/router-u5O5KCQr.mjs +156 -0
  149. package/dist/runtime/mod.cjs +1 -1
  150. package/dist/runtime/mod.js +1 -1
  151. package/dist/secured-document-CxzZZ9nL.mjs +35 -0
  152. package/dist/send-C4dDpAxm.mjs +495 -0
  153. package/dist/sig/accept.test.mjs +1 -1
  154. package/dist/sig/compound-proof-discovery.test.d.mts +2 -0
  155. package/dist/sig/compound-proof-discovery.test.mjs +331 -0
  156. package/dist/sig/compound-proof-producer.test.d.mts +2 -0
  157. package/dist/sig/compound-proof-producer.test.mjs +107 -0
  158. package/dist/sig/compound-proof-verification.test.d.mts +2 -0
  159. package/dist/sig/compound-proof-verification.test.mjs +833 -0
  160. package/dist/sig/compound-proof.test.d.mts +2 -0
  161. package/dist/sig/compound-proof.test.mjs +411 -0
  162. package/dist/sig/http.test.mjs +702 -31
  163. package/dist/sig/key-owner-evidence.test.d.mts +2 -0
  164. package/dist/sig/key-owner-evidence.test.mjs +100 -0
  165. package/dist/sig/key.test.mjs +220 -8
  166. package/dist/sig/ld.test.mjs +65 -7
  167. package/dist/sig/mitra-receive-interop.test.d.mts +2 -0
  168. package/dist/sig/mitra-receive-interop.test.mjs +260 -0
  169. package/dist/sig/mitra-send-interop.test.d.mts +2 -0
  170. package/dist/sig/mitra-send-interop.test.mjs +461 -0
  171. package/dist/sig/mod.cjs +18 -16
  172. package/dist/sig/mod.d.cts +4 -4
  173. package/dist/sig/mod.d.ts +4 -4
  174. package/dist/sig/mod.js +3 -3
  175. package/dist/sig/owner.test.mjs +108 -9
  176. package/dist/sig/portable-collection.test.d.mts +2 -0
  177. package/dist/sig/portable-collection.test.mjs +697 -0
  178. package/dist/sig/portable-interop.test.d.mts +2 -0
  179. package/dist/sig/portable-interop.test.mjs +156 -0
  180. package/dist/sig/portable-key-id.test.d.mts +2 -0
  181. package/dist/sig/portable-key-id.test.mjs +67 -0
  182. package/dist/sig/portable-key.test.d.mts +2 -0
  183. package/dist/sig/portable-key.test.mjs +1439 -0
  184. package/dist/sig/proof.test.mjs +1753 -16
  185. package/dist/sig/tootik-request-interop.test.d.mts +2 -0
  186. package/dist/sig/tootik-request-interop.test.mjs +190 -0
  187. package/dist/sig/tootik-send-interop.test.d.mts +2 -0
  188. package/dist/sig/tootik-send-interop.test.mjs +331 -0
  189. package/dist/sig/verification.test.d.mts +2 -0
  190. package/dist/sig/verification.test.mjs +310 -0
  191. package/dist/std__assert-DvMDRPes.mjs +8 -0
  192. package/dist/{testing-CO7ftO8A.mjs → tasks-IG3pHfvZ.mjs} +2 -2
  193. package/dist/{temporal-fsXO5IvD.mjs → temporal-D0KJI4NF.mjs} +3 -3
  194. package/dist/testing/context.test.d.mts +2 -0
  195. package/dist/testing/context.test.mjs +19 -0
  196. package/dist/testing/kv.test.d.mts +2 -0
  197. package/dist/testing/kv.test.mjs +101 -0
  198. package/dist/testing/mod.d.mts +1617 -48
  199. package/dist/testing/mod.mjs +6 -3
  200. package/dist/testing-t8xDeJZ5.mjs +8 -0
  201. package/dist/{transformers-DGwl-hD1.cjs → transformers-BalCrSsr.cjs} +0 -1
  202. package/dist/{types-DC1pNgJA.js → types-_S2_MeLF.js} +144 -8
  203. package/dist/{types-CPym83zc.cjs → types-poIao40f.cjs} +160 -7
  204. package/dist/utils/docloader.test.mjs +530 -8
  205. package/dist/utils/kv-cache.test.mjs +2 -2
  206. package/dist/utils/mod.cjs +1 -1
  207. package/dist/utils/mod.d.cts +1 -1
  208. package/dist/utils/mod.d.ts +1 -1
  209. package/dist/utils/mod.js +1 -1
  210. package/dist/verification-C3954hQh.mjs +5 -0
  211. package/dist/verification-Dpfcw8ml.mjs +105 -0
  212. package/dist/vocab/cjs.test.mjs +1 -1
  213. package/package.json +8 -8
  214. package/dist/assert_rejects-EgstcQy5.mjs +0 -82
  215. package/dist/docloader-e1khNUKy.mjs +0 -49
  216. package/dist/http-CsmQ-BQ4.js +0 -2483
  217. package/dist/http-VyDTd4G3.d.cts +0 -553
  218. package/dist/http-hlIjmewS.cjs +0 -2694
  219. package/dist/http-lf8Hsd91.d.ts +0 -553
  220. package/dist/key-DfPsX8hv.mjs +0 -453
  221. package/dist/keycache-_DHqz5z_.mjs +0 -97
  222. package/dist/metrics-CoUJLQhZ.d.cts +0 -276
  223. package/dist/metrics-Dc4jR-IC.d.ts +0 -276
  224. package/dist/mod-B0hW12_O.d.cts +0 -307
  225. package/dist/mod-COIAjwRS.d.ts +0 -307
  226. package/dist/owner-Br9YYzn_.mjs +0 -120
  227. package/dist/proof-B4ngoaNQ.mjs +0 -316
  228. package/dist/proof-B6E0XUXJ.js +0 -1415
  229. package/dist/proof-NN4xCQGL.cjs +0 -1562
  230. package/dist/send-CxHKUNzv.mjs +0 -239
  231. package/dist/std__assert-B2NnY9bq.mjs +0 -79
  232. package/dist/temporal-polyfill-BCdTHexd.cjs +0 -3565
  233. /package/dist/{accept-8PiolDP7.mjs → accept-CPVqki78.mjs} +0 -0
  234. /package/dist/{activity-listener-BtTSxZWA.mjs → activity-listener-C-BSbKI7.mjs} +0 -0
  235. /package/dist/{client-CAM_bQXx.d.cts → client-D-XP_Ccb.d.cts} +0 -0
  236. /package/dist/{client-CSddvgWN.d.ts → client-D-XP_Ccb.d.ts} +0 -0
  237. /package/dist/{keys-Dbyd7bhl.mjs → keys-DIJNHW-Y.mjs} +0 -0
  238. /package/dist/{kv-CXBAok3k.mjs → kv-DCHkaJ9-.mjs} +0 -0
  239. /package/dist/{negotiation-RuwrPFqi.mjs → negotiation-CCocWT_N.mjs} +0 -0
  240. /package/dist/{retry-DgFqeS5k.mjs → retry-zX5xw5e0.mjs} +0 -0
  241. /package/dist/{chunk-D5IdezsZ.js → rolldown-runtime-BC-Eo5Mj.js} +0 -0
  242. /package/dist/{chunk-Czpeue4M.cjs → rolldown-runtime-Dmm3t92W.cjs} +0 -0
  243. /package/dist/{transformers-DXze6FUB.js → transformers-DAtXl_Kc.js} +0 -0
  244. /package/dist/{types-CrtdVuDP.mjs → types-DE_LV1JB.mjs} +0 -0
@@ -1,15 +1,18 @@
1
1
  import { Temporal } from "temporal-polyfill";
2
2
  import "urlpattern-polyfill";
3
3
  globalThis.addEventListener = () => {};
4
- import { t as esm_default } from "../esm-yYQfPhIY.mjs";
5
- import { t as assertEquals } from "../assert_equals-BV7oLMxm.mjs";
6
- import "../std__assert-B2NnY9bq.mjs";
7
- import { t as assertRejects } from "../assert_rejects-EgstcQy5.mjs";
8
- import { l as verifyRequest } from "../http-CPVMUgsW.mjs";
9
- import { i as rsaPrivateKey2 } from "../keys-Dbyd7bhl.mjs";
10
- import { t as getAuthenticatedDocumentLoader } from "../docloader-e1khNUKy.mjs";
4
+ import { t as esm_default } from "../esm-BHXXx4Wx.mjs";
5
+ import { t as assertEquals } from "../assert_equals-Z6IcEbWU.mjs";
6
+ import "../std__assert-DvMDRPes.mjs";
7
+ import { t as assertRejects } from "../assert_rejects-CTptdYck.mjs";
8
+ import { f as verifyRequest } from "../http-siciWkmx.mjs";
9
+ import { i as rsaPrivateKey2 } from "../keys-DIJNHW-Y.mjs";
10
+ import { t as getAuthenticatedDocumentLoader } from "../docloader-DBoYbvYW.mjs";
11
+ import { configure, reset } from "@logtape/logtape";
12
+ import { FetchError, UrlError } from "@fedify/vocab-runtime";
11
13
  import { mockDocumentLoader, test } from "@fedify/fixture";
12
- import { UrlError } from "@fedify/vocab-runtime";
14
+ import dns from "node:dns/promises";
15
+ import { deepStrictEqual, ok, rejects } from "node:assert/strict";
13
16
  //#region src/utils/docloader.test.ts
14
17
  test("getAuthenticatedDocumentLoader()", async (t) => {
15
18
  esm_default.spyGlobal();
@@ -63,6 +66,39 @@ test("getAuthenticatedDocumentLoader()", async (t) => {
63
66
  esm_default.hardReset();
64
67
  });
65
68
  });
69
+ test("getAuthenticatedDocumentLoader() validates redirects", async (t) => {
70
+ esm_default.spyGlobal();
71
+ let privateRequestCount = 0;
72
+ esm_default.get("https://example.com/redirect-to-private", () => Response.redirect("http://localhost/private", 302));
73
+ esm_default.get("http://localhost/private", () => {
74
+ privateRequestCount++;
75
+ return Response.json({ private: true });
76
+ });
77
+ await t.step("deny public-to-private redirects", async () => {
78
+ const loader = getAuthenticatedDocumentLoader({
79
+ keyId: new URL("https://example.com/key2"),
80
+ privateKey: rsaPrivateKey2
81
+ });
82
+ await assertRejects(() => loader("https://example.com/redirect-to-private"), UrlError);
83
+ assertEquals(privateRequestCount, 0);
84
+ });
85
+ esm_default.get("https://example.com/redirect-to-public", () => Response.redirect("https://www.example.com/document", 302));
86
+ esm_default.get("https://www.example.com/document", () => Response.json({ public: true }));
87
+ await t.step("allow public-to-public redirects", async () => {
88
+ assertEquals((await getAuthenticatedDocumentLoader({
89
+ keyId: new URL("https://example.com/key2"),
90
+ privateKey: rsaPrivateKey2
91
+ })("https://example.com/redirect-to-public")).document, { public: true });
92
+ });
93
+ await t.step("allow private redirects when explicitly enabled", async () => {
94
+ assertEquals((await getAuthenticatedDocumentLoader({
95
+ keyId: new URL("https://example.com/key2"),
96
+ privateKey: rsaPrivateKey2
97
+ }, { allowPrivateAddress: true })("https://example.com/redirect-to-private")).document, { private: true });
98
+ assertEquals(privateRequestCount, 1);
99
+ });
100
+ esm_default.hardReset();
101
+ });
66
102
  test("getAuthenticatedDocumentLoader() cancellation", {
67
103
  sanitizeResources: false,
68
104
  sanitizeOps: false
@@ -103,5 +139,491 @@ test("getAuthenticatedDocumentLoader() cancellation", {
103
139
  });
104
140
  esm_default.hardReset();
105
141
  });
142
+ test("getAuthenticatedDocumentLoader() bounds JSON after redirects", async () => {
143
+ esm_default.mockGlobal();
144
+ let oversized = true;
145
+ try {
146
+ const url = "https://example.com/bounded";
147
+ esm_default.get(url, {
148
+ status: 302,
149
+ headers: { Location: `${url}/document` }
150
+ });
151
+ esm_default.get(`${url}/document`, () => new Response("{\"name\":\"hello\"}", { headers: {
152
+ "Content-Type": "application/activity+json",
153
+ ...oversized ? { "Content-Length": String(16777217) } : {}
154
+ } }));
155
+ const loader = getAuthenticatedDocumentLoader({
156
+ privateKey: rsaPrivateKey2,
157
+ keyId: new URL("https://example.com/key2")
158
+ }, { allowPrivateAddress: true });
159
+ await assertRejects(() => loader(url), FetchError);
160
+ oversized = false;
161
+ assertEquals((await loader(url)).document, { name: "hello" });
162
+ } finally {
163
+ esm_default.hardReset();
164
+ }
165
+ });
166
+ test("authenticated document loader suppresses HTTP failure logs", async () => {
167
+ const records = [];
168
+ const url = "https://example.com/missing-authenticated-document";
169
+ const loader = getAuthenticatedDocumentLoader({
170
+ keyId: new URL("https://example.com/key2"),
171
+ privateKey: rsaPrivateKey2
172
+ }, { allowPrivateAddress: true });
173
+ await reset();
174
+ await configure({
175
+ sinks: { capture: (record) => records.push(record) },
176
+ loggers: [{
177
+ category: ["fedify"],
178
+ lowestLevel: "debug",
179
+ sinks: ["capture"]
180
+ }]
181
+ });
182
+ esm_default.mockGlobal();
183
+ esm_default.get(url, { status: 404 });
184
+ try {
185
+ for (const suppressError of [
186
+ true,
187
+ false,
188
+ void 0
189
+ ]) {
190
+ records.length = 0;
191
+ assertEquals((await assertRejects(() => loader(url, { suppressError }), FetchError)).response?.status, 404);
192
+ const failures = records.filter((record) => record.rawMessage === "Failed to fetch document: {status} {url} {headers}");
193
+ assertEquals(failures.length, 1);
194
+ assertEquals(failures[0].level, suppressError ? "warning" : "error");
195
+ if (suppressError) assertEquals(records.filter((record) => record.level === "error"), []);
196
+ }
197
+ } finally {
198
+ esm_default.hardReset();
199
+ await reset();
200
+ }
201
+ });
202
+ test("authenticated document loader suppresses private URL logs", async () => {
203
+ const records = [];
204
+ const loader = getAuthenticatedDocumentLoader({
205
+ keyId: new URL("https://example.com/key2"),
206
+ privateKey: rsaPrivateKey2
207
+ });
208
+ await reset();
209
+ await configure({
210
+ sinks: { capture: (record) => {
211
+ records.push(record);
212
+ } },
213
+ loggers: [{
214
+ category: ["fedify"],
215
+ lowestLevel: "debug",
216
+ sinks: ["capture"]
217
+ }]
218
+ });
219
+ esm_default.spyGlobal();
220
+ let requests = 0;
221
+ esm_default.get("https://example.com/private-redirect", () => {
222
+ requests++;
223
+ return Response.redirect("http://127.0.0.1/private", 302);
224
+ });
225
+ try {
226
+ for (const url of ["http://127.0.0.1/private", "https://example.com/private-redirect"]) for (const suppressError of [
227
+ true,
228
+ false,
229
+ void 0
230
+ ]) {
231
+ records.length = 0;
232
+ requests = 0;
233
+ await assertRejects(() => loader(url, { suppressError }), UrlError);
234
+ const failures = records.filter((r) => r.rawMessage === "Disallowed private URL: {url}");
235
+ assertEquals(failures.length, 1);
236
+ assertEquals(failures[0].level, suppressError ? "warning" : "error");
237
+ assertEquals(requests, url.startsWith("http://127") ? 0 : 1);
238
+ }
239
+ } finally {
240
+ esm_default.hardReset();
241
+ await reset();
242
+ }
243
+ });
244
+ test("getAuthenticatedDocumentLoader() logs DNS failures as such", { ignore: "Deno" in globalThis && (await Deno.permissions.query({ name: "net" })).state !== "granted" }, async (t) => {
245
+ const loader = getAuthenticatedDocumentLoader({
246
+ keyId: new URL("https://example.com/key2"),
247
+ privateKey: rsaPrivateKey2
248
+ });
249
+ for (const result of [
250
+ "throws",
251
+ "empty",
252
+ "private"
253
+ ]) await t.step(result, async () => {
254
+ const originalLookup = dns.lookup;
255
+ dns.lookup = (() => result === "throws" ? Promise.reject(/* @__PURE__ */ new Error("Resolver unavailable")) : Promise.resolve(result === "empty" ? [] : [{
256
+ address: "127.0.0.1",
257
+ family: 4
258
+ }]));
259
+ const records = [];
260
+ await configure({
261
+ sinks: { buffer: (record) => records.push(record) },
262
+ loggers: [{
263
+ category: "fedify",
264
+ sinks: ["buffer"],
265
+ lowestLevel: "debug"
266
+ }, {
267
+ category: ["logtape", "meta"],
268
+ sinks: []
269
+ }],
270
+ reset: true
271
+ });
272
+ try {
273
+ const url = "https://dns-failure.invalid/object";
274
+ const error = await assertRejects(() => loader(url), UrlError);
275
+ assertEquals(error.reason, result === "private" ? "disallowed" : "dns");
276
+ assertEquals(records.map((r) => [
277
+ r.level,
278
+ r.rawMessage,
279
+ r.properties.url
280
+ ]), [result === "private" ? [
281
+ "error",
282
+ "Disallowed private URL: {url}",
283
+ url
284
+ ] : [
285
+ "debug",
286
+ "DNS lookup failed for {url}",
287
+ url
288
+ ]]);
289
+ assertEquals(records[0].properties.error, error);
290
+ } finally {
291
+ await reset();
292
+ dns.lookup = originalLookup;
293
+ }
294
+ });
295
+ });
296
+ test("getAuthenticatedDocumentLoader() bounds alternate document chains", async (t) => {
297
+ const base = "https://example.com/alternate-chain/";
298
+ const loader = getAuthenticatedDocumentLoader({
299
+ keyId: new URL("https://example.com/key2"),
300
+ privateKey: rsaPrivateKey2
301
+ }, { allowPrivateAddress: true });
302
+ for (const mode of [
303
+ "header",
304
+ "html",
305
+ "mixed"
306
+ ]) for (const hops of [20, 21]) await t.step(`${mode}: ${hops} hops`, async () => {
307
+ esm_default.mockGlobal();
308
+ let requests = 0;
309
+ esm_default.get(`begin:${base}`, ({ url }) => {
310
+ requests++;
311
+ const index = Number(new URL(url).pathname.split("/").at(-1));
312
+ if (index === hops || requests > 30) return Response.json({ done: true });
313
+ const next = `${base}${index + 1}`;
314
+ if (mode === "mixed" && index % 2 === 1) return Response.redirect(next, 302);
315
+ return alternate(next, mode === "html");
316
+ });
317
+ try {
318
+ if (hops === 20) deepStrictEqual((await loader(`${base}0`)).document, { done: true });
319
+ else await rejects(loader(`${base}0`), (error) => {
320
+ ok(error instanceof FetchError);
321
+ ok(error.message.includes("Too many redirections (21)"));
322
+ return true;
323
+ });
324
+ deepStrictEqual(requests, 21);
325
+ } finally {
326
+ esm_default.hardReset();
327
+ }
328
+ });
329
+ for (const mode of [
330
+ "header",
331
+ "html",
332
+ "alternate-redirect",
333
+ "redirect-alternate",
334
+ "redirect-intermediate"
335
+ ]) await t.step(`${mode}: cycle`, async () => {
336
+ esm_default.mockGlobal();
337
+ let requests = 0;
338
+ esm_default.get(`begin:${base}`, ({ url }) => {
339
+ requests++;
340
+ if (requests > 30) return Response.json({ stopped: true });
341
+ const index = Number(new URL(url).pathname.split("/").at(-1));
342
+ const next = `${base}${mode === "redirect-intermediate" ? index === 0 ? 1 : index === 1 ? 2 : 1 : 1 - index}`;
343
+ if (mode === "alternate-redirect" && index === 1 || (mode === "redirect-alternate" || mode === "redirect-intermediate") && index === 0) return Response.redirect(next, 302);
344
+ return alternate(next, mode === "html");
345
+ });
346
+ try {
347
+ await rejects(loader(`${base}0`), (error) => {
348
+ ok(error instanceof FetchError);
349
+ ok(error.message.includes("Redirect loop detected:"));
350
+ return true;
351
+ });
352
+ deepStrictEqual(requests, mode === "redirect-intermediate" ? 3 : 2);
353
+ } finally {
354
+ esm_default.hardReset();
355
+ }
356
+ });
357
+ await t.step("alternate followed by 20 redirects shares the limit", async () => {
358
+ esm_default.mockGlobal();
359
+ let requests = 0;
360
+ esm_default.get(`begin:${base}`, ({ url }) => {
361
+ requests++;
362
+ const index = Number(new URL(url).pathname.split("/").at(-1));
363
+ if (index === 21) return Response.json({ done: true });
364
+ return index === 0 ? alternate(`${base}1`, false) : Response.redirect(`${base}${index + 1}`, 302);
365
+ });
366
+ try {
367
+ await rejects(loader(`${base}0`), (error) => {
368
+ ok(error instanceof FetchError);
369
+ ok(error.message.includes("Too many redirections (21)"));
370
+ return true;
371
+ });
372
+ deepStrictEqual(requests, 21);
373
+ } finally {
374
+ esm_default.hardReset();
375
+ }
376
+ });
377
+ await t.step("relative alternate after redirect and isolated calls", async () => {
378
+ esm_default.mockGlobal();
379
+ esm_default.get(`${base}start`, Response.redirect(`${base}html`, 302));
380
+ esm_default.get(`${base}html`, alternate("./document", true));
381
+ esm_default.get(`${base}document`, Response.json({ done: true }));
382
+ try {
383
+ for (let i = 0; i < 2; i++) {
384
+ const results = await Promise.all([loader(`${base}start`), loader(`${base}start`)]);
385
+ for (const result of results) {
386
+ deepStrictEqual(result.document, { done: true });
387
+ deepStrictEqual(result.documentUrl, `${base}document`);
388
+ }
389
+ }
390
+ } finally {
391
+ esm_default.hardReset();
392
+ }
393
+ });
394
+ function alternate(next, html) {
395
+ return html ? new Response(`<link rel="alternate" type="application/activity+json" href="${next}">`, { headers: { "Content-Type": "text/html" } }) : new Response("not JSON", { headers: {
396
+ "Content-Type": "text/plain",
397
+ Link: `<${next}>; rel="alternate"; type="application/activity+json"`
398
+ } });
399
+ }
400
+ });
401
+ test("getAuthenticatedDocumentLoader() preserves cancellation across alternates", async (t) => {
402
+ const base = "https://example.com/alternate-abort/";
403
+ const loader = getAuthenticatedDocumentLoader({
404
+ keyId: new URL("https://example.com/key2"),
405
+ privateKey: rsaPrivateKey2
406
+ }, { allowPrivateAddress: true });
407
+ for (const html of [false, true]) await t.step(html ? "HTML" : "Link header", async () => {
408
+ esm_default.mockGlobal();
409
+ const controller = new AbortController();
410
+ const reason = /* @__PURE__ */ new Error("Alternate document loading cancelled");
411
+ let requests = 0;
412
+ esm_default.get(`begin:${base}`, ({ url }) => {
413
+ requests++;
414
+ const index = Number(new URL(url).pathname.split("/").at(-1));
415
+ if (index === 2) return Response.json({ done: true });
416
+ if (index === 1) controller.abort(reason);
417
+ const next = `${base}${index + 1}`;
418
+ return new Response(html ? `<link rel="alternate" type="application/activity+json" href="${next}">` : "not JSON", { headers: html ? { "Content-Type": "text/html" } : {
419
+ "Content-Type": "text/plain",
420
+ Link: `<${next}>; rel="alternate"; type="application/activity+json"`
421
+ } });
422
+ });
423
+ try {
424
+ await rejects(loader(`${base}0`, { signal: controller.signal }), (error) => {
425
+ ok(error === reason);
426
+ return true;
427
+ });
428
+ deepStrictEqual(requests, 2);
429
+ } finally {
430
+ esm_default.hardReset();
431
+ }
432
+ });
433
+ });
434
+ test("getAuthenticatedDocumentLoader() tracks fallback redirects after alternates", async () => {
435
+ esm_default.mockGlobal();
436
+ let requests = 0;
437
+ let knocks = 0;
438
+ const first = "https://example.com/fallback-alternate/a";
439
+ const second = "https://example.com/fallback-alternate/b";
440
+ esm_default.get(first, () => {
441
+ requests++;
442
+ if (requests > 30) return Response.json({ stopped: true });
443
+ return new Response("", { headers: {
444
+ "Content-Type": "text/plain",
445
+ Link: `<${second}>; rel="alternate"; type="application/activity+json"`
446
+ } });
447
+ });
448
+ esm_default.get(second, () => {
449
+ requests++;
450
+ return ++knocks % 2 === 1 ? new Response("", { status: 401 }) : Response.redirect(first, 302);
451
+ });
452
+ try {
453
+ const loader = getAuthenticatedDocumentLoader({
454
+ keyId: new URL("https://example.com/key2"),
455
+ privateKey: rsaPrivateKey2
456
+ }, { allowPrivateAddress: true });
457
+ await assertRejects(() => loader(first), FetchError, "Redirect loop detected:");
458
+ assertEquals(requests, 3);
459
+ } finally {
460
+ esm_default.hardReset();
461
+ }
462
+ });
463
+ test("getAuthenticatedDocumentLoader() resolves relative alternates without response URLs", async () => {
464
+ const originalFetch = globalThis.fetch;
465
+ const base = "https://example.com/empty-response-url/";
466
+ const urls = [];
467
+ globalThis.fetch = (input) => {
468
+ const url = input instanceof Request ? input.url : String(input);
469
+ urls.push(url);
470
+ if (url === `${base}start`) return Promise.resolve(Response.redirect(`${base}nested/page`, 302));
471
+ if (url === `${base}nested/page`) return Promise.resolve(new Response("<link rel=\"alternate\" type=\"application/activity+json\" href=\"document\">", { headers: { "Content-Type": "text/html" } }));
472
+ assertEquals(url, `${base}nested/document`);
473
+ return Promise.resolve(Response.json({ done: true }));
474
+ };
475
+ try {
476
+ assertEquals(await getAuthenticatedDocumentLoader({
477
+ keyId: new URL("https://example.com/key2"),
478
+ privateKey: rsaPrivateKey2
479
+ }, { allowPrivateAddress: true })(`${base}start`), {
480
+ contextUrl: null,
481
+ document: { done: true },
482
+ documentUrl: `${base}nested/document`
483
+ });
484
+ assertEquals(urls, [
485
+ `${base}start`,
486
+ `${base}nested/page`,
487
+ `${base}nested/document`
488
+ ]);
489
+ } finally {
490
+ globalThis.fetch = originalFetch;
491
+ }
492
+ });
493
+ test("getAuthenticatedDocumentLoader() validates alternate addresses", async () => {
494
+ esm_default.mockGlobal();
495
+ let privateRequests = 0;
496
+ const url = "https://8.8.8.8/alternate";
497
+ esm_default.get(url, new Response("", { headers: {
498
+ "Content-Type": "text/plain",
499
+ Link: "<http://127.0.0.1/private>; rel=\"alternate\"; type=\"application/activity+json\""
500
+ } }));
501
+ esm_default.get("http://127.0.0.1/private", () => {
502
+ privateRequests++;
503
+ return Response.json({ private: true });
504
+ });
505
+ try {
506
+ const loader = getAuthenticatedDocumentLoader({
507
+ keyId: new URL("https://example.com/key2"),
508
+ privateKey: rsaPrivateKey2
509
+ });
510
+ await assertRejects(() => loader(url), UrlError);
511
+ assertEquals(privateRequests, 0);
512
+ } finally {
513
+ esm_default.hardReset();
514
+ }
515
+ });
516
+ test("getAuthenticatedDocumentLoader() rejects cancellation before fetching", async () => {
517
+ esm_default.mockGlobal();
518
+ let requests = 0;
519
+ const url = "https://example.com/pre-aborted-alternate";
520
+ esm_default.get(url, () => {
521
+ requests++;
522
+ return Response.json({ done: true });
523
+ });
524
+ try {
525
+ const loader = getAuthenticatedDocumentLoader({
526
+ keyId: new URL("https://example.com/key2"),
527
+ privateKey: rsaPrivateKey2
528
+ }, { allowPrivateAddress: true });
529
+ const controller = new AbortController();
530
+ controller.abort();
531
+ await rejects(loader(url, { signal: controller.signal }), { name: "AbortError" });
532
+ deepStrictEqual(requests, 0);
533
+ } finally {
534
+ esm_default.hardReset();
535
+ }
536
+ });
537
+ function assertTimeoutFetchError(error, url) {
538
+ ok(error instanceof FetchError, String(error));
539
+ deepStrictEqual(error.url.href, url);
540
+ deepStrictEqual(error.response, void 0);
541
+ ok(error.cause instanceof DOMException);
542
+ deepStrictEqual(error.cause.name, "TimeoutError");
543
+ return true;
544
+ }
545
+ test("getAuthenticatedDocumentLoader() times out", async (t) => {
546
+ const identity = {
547
+ keyId: new URL("https://example.com/key2"),
548
+ privateKey: rsaPrivateKey2
549
+ };
550
+ const delay = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
551
+ await t.step("a request without a response", async () => {
552
+ esm_default.mockGlobal();
553
+ const url = "https://example.com/hang";
554
+ esm_default.get(url, () => new Promise(() => {}));
555
+ try {
556
+ await rejects(getAuthenticatedDocumentLoader(identity, {
557
+ allowPrivateAddress: true,
558
+ timeout: 100
559
+ })(url), (e) => assertTimeoutFetchError(e, url));
560
+ } finally {
561
+ esm_default.hardReset();
562
+ }
563
+ });
564
+ await t.step("double-knocking shares the timeout", async () => {
565
+ esm_default.mockGlobal();
566
+ const url = "https://example.com/double-knock";
567
+ let requests = 0;
568
+ esm_default.get(url, async () => {
569
+ requests++;
570
+ await delay(200);
571
+ return new Response(null, { status: 401 });
572
+ });
573
+ try {
574
+ await rejects(getAuthenticatedDocumentLoader(identity, {
575
+ allowPrivateAddress: true,
576
+ timeout: 300
577
+ })(url), (e) => assertTimeoutFetchError(e, url));
578
+ deepStrictEqual(requests, 2);
579
+ await delay(250);
580
+ deepStrictEqual(requests, 2);
581
+ } finally {
582
+ esm_default.hardReset();
583
+ }
584
+ });
585
+ for (const step of ["determineSpec", "rememberSpec"]) await t.step(`a slow ${step}()`, async () => {
586
+ esm_default.mockGlobal();
587
+ const url = `https://example.com/slow-${step}`;
588
+ let requests = 0;
589
+ esm_default.get(url, () => {
590
+ requests++;
591
+ return Response.json({ parsed: true });
592
+ });
593
+ try {
594
+ await rejects(getAuthenticatedDocumentLoader(identity, {
595
+ allowPrivateAddress: true,
596
+ timeout: 50,
597
+ specDeterminer: {
598
+ async determineSpec() {
599
+ if (step === "determineSpec") await delay(150);
600
+ return "rfc9421";
601
+ },
602
+ async rememberSpec() {
603
+ if (step === "rememberSpec") await delay(150);
604
+ }
605
+ }
606
+ })(url), (e) => assertTimeoutFetchError(e, url));
607
+ await delay(200);
608
+ deepStrictEqual(requests, step === "determineSpec" ? 0 : 1);
609
+ } finally {
610
+ esm_default.hardReset();
611
+ }
612
+ });
613
+ await t.step("the caller's signal still aborts", async () => {
614
+ esm_default.mockGlobal();
615
+ const url = "https://example.com/hang";
616
+ esm_default.get(url, () => new Promise(() => {}));
617
+ try {
618
+ const loader = getAuthenticatedDocumentLoader(identity, { allowPrivateAddress: true });
619
+ const controller = new AbortController();
620
+ const reason = /* @__PURE__ */ new Error("Canceled by the caller");
621
+ setTimeout(() => controller.abort(reason), 50);
622
+ await rejects(loader(url, { signal: controller.signal }), (e) => e === reason);
623
+ } finally {
624
+ esm_default.hardReset();
625
+ }
626
+ });
627
+ });
106
628
  //#endregion
107
629
  export {};
@@ -1,10 +1,10 @@
1
1
  import { Temporal } from "temporal-polyfill";
2
2
  import { URLPattern } from "urlpattern-polyfill";
3
3
  globalThis.addEventListener = () => {};
4
- import { n as kvCache, t as MockKvStore } from "../kv-cache-KyGqJqSp.mjs";
4
+ import { n as kvCache, t as MockKvStore } from "../kv-cache-BF1hnW65.mjs";
5
+ import { preloadedContexts } from "@fedify/vocab-runtime";
5
6
  import { deepStrictEqual, throws } from "node:assert";
6
7
  import { createTestMeterProvider, mockDocumentLoader, test } from "@fedify/fixture";
7
- import { preloadedContexts } from "@fedify/vocab-runtime";
8
8
  //#region src/utils/kv-cache.test.ts
9
9
  test("kvCache()", async (t) => {
10
10
  const kv = new MockKvStore();
@@ -1,5 +1,5 @@
1
1
  const { URLPattern } = require("urlpattern-polyfill");
2
2
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
- const require_kv_cache = require("../kv-cache-DQ29C4XL.cjs");
3
+ const require_kv_cache = require("../kv-cache-DCac1nEU.cjs");
4
4
  exports.getAuthenticatedDocumentLoader = require_kv_cache.getAuthenticatedDocumentLoader;
5
5
  exports.kvCache = require_kv_cache.kvCache;
@@ -1,3 +1,3 @@
1
1
  /// <reference lib="esnext.temporal" />
2
- import { n as getAuthenticatedDocumentLoader, t as kvCache } from "../mod-BtEaIEcD.cjs";
2
+ import { n as getAuthenticatedDocumentLoader, t as kvCache } from "../mod-Dw-nPxQh.cjs";
3
3
  export { getAuthenticatedDocumentLoader, kvCache };
@@ -1,3 +1,3 @@
1
1
  /// <reference lib="esnext.temporal" />
2
- import { n as getAuthenticatedDocumentLoader, t as kvCache } from "../mod-UMmveiO0.js";
2
+ import { n as getAuthenticatedDocumentLoader, t as kvCache } from "../mod-DEZXwV6J.js";
3
3
  export { getAuthenticatedDocumentLoader, kvCache };
package/dist/utils/mod.js CHANGED
@@ -1,4 +1,4 @@
1
1
  import { Temporal } from "temporal-polyfill";
2
2
  import { URLPattern } from "urlpattern-polyfill";
3
- import { n as getAuthenticatedDocumentLoader, t as kvCache } from "../kv-cache-5bnX3-Jq.js";
3
+ import { n as getAuthenticatedDocumentLoader, t as kvCache } from "../kv-cache-CTQr1tbn.js";
4
4
  export { getAuthenticatedDocumentLoader, kvCache };
@@ -0,0 +1,5 @@
1
+ import "temporal-polyfill";
2
+ import "urlpattern-polyfill";
3
+ globalThis.addEventListener = () => {};
4
+ import { a as verificationObservation, r as snapshotKey } from "./verification-Dpfcw8ml.mjs";
5
+ export { snapshotKey, verificationObservation };