@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
@@ -0,0 +1,422 @@
1
+ import "temporal-polyfill";
2
+ import "urlpattern-polyfill";
3
+ globalThis.addEventListener = () => {};
4
+ import { t as MemoryKvStore } from "../kv-DCHkaJ9-.mjs";
5
+ import { t as assertEquals } from "../assert_equals-Z6IcEbWU.mjs";
6
+ import { t as assertFalse } from "../assert_false-CPdRnc3E.mjs";
7
+ import { t as assertInstanceOf } from "../assert_instance_of-mrXWWNY-.mjs";
8
+ import { t as assertThrows } from "../assert_throws-BYITOvPO.mjs";
9
+ import "../std__assert-DvMDRPes.mjs";
10
+ import { t as assertStrictEquals } from "../assert_strict_equals-CYvBmkpC.mjs";
11
+ import { t as assert } from "../assert-D1Eafmd_.mjs";
12
+ import { r as ed25519PublicKey } from "../keys-DIJNHW-Y.mjs";
13
+ import { o as createFederation, x as parseHashlinkGatewayRequest } from "../middleware-D7_LO-Vs.mjs";
14
+ import { r as createFederationBuilder } from "../builder-Bda6LK7A.mjs";
15
+ import { n as RouterError } from "../router-u5O5KCQr.mjs";
16
+ import { Note } from "@fedify/vocab";
17
+ import { computeDigestMultibase, encodeMultibase, exportDidKey } from "@fedify/vocab-runtime";
18
+ import { createTestMeterProvider, mockDocumentLoader, test } from "@fedify/fixture";
19
+ //#region src/federation/hashlink-media.test.ts
20
+ const textDecoder = new TextDecoder();
21
+ const FEP_DIGEST = "zQmdfTbBqBPQ7VNxZEYEj14VmRuZBkqFbiwReogJgS1zR1n";
22
+ const mediaBytes = new TextEncoder().encode("Hello, portable world!");
23
+ const mediaDigest = await computeDigestMultibase(mediaBytes);
24
+ const mediaSha256 = new Uint8Array(await crypto.subtle.digest("SHA-256", mediaBytes));
25
+ const ffDigest = (/* @__PURE__ */ new Uint8Array(32)).fill(255);
26
+ const ffMultihash = new Uint8Array([
27
+ 18,
28
+ 32,
29
+ ...ffDigest
30
+ ]);
31
+ const ffBase64 = textDecoder.decode(encodeMultibase("base64", ffMultihash));
32
+ const ffBase58 = textDecoder.decode(encodeMultibase("base58btc", ffMultihash));
33
+ function mediaUrl(hashlink) {
34
+ return `https://example.com/.well-known/apgateway/${hashlink}`;
35
+ }
36
+ function parse(url) {
37
+ return parseHashlinkGatewayRequest(new URL(url));
38
+ }
39
+ function assertMedia(url) {
40
+ const result = parse(url);
41
+ assert(result != null, `${url} should be a hashlink media request`);
42
+ if (result.type !== "media") throw new Error(`${url} should not be malformed: ${result.error}`);
43
+ return result.media;
44
+ }
45
+ function assertMalformed(url) {
46
+ const result = parse(url);
47
+ assert(result != null, `${url} should be a hashlink media request`);
48
+ assertEquals(result.type, "malformed", url);
49
+ if (result.type === "malformed") assertInstanceOf(result.error, TypeError);
50
+ }
51
+ function createTestFederation() {
52
+ return createFederation({
53
+ kv: new MemoryKvStore(),
54
+ documentLoaderFactory: () => mockDocumentLoader,
55
+ contextLoaderFactory: () => mockDocumentLoader
56
+ });
57
+ }
58
+ test("parseHashlinkGatewayRequest()", async (t) => {
59
+ await t.step("parses a hashlink", () => {
60
+ const media = assertMedia(mediaUrl(`hl:${mediaDigest}`));
61
+ assertEquals(media.hashlink, `hl:${mediaDigest}`);
62
+ assertEquals(media.digestMultibase, mediaDigest);
63
+ assertEquals(media.algorithm, "sha2-256");
64
+ assertEquals(media.digest, mediaSha256);
65
+ assertEquals(media.multihash, new Uint8Array([
66
+ 18,
67
+ 32,
68
+ ...mediaSha256
69
+ ]));
70
+ assert(Object.isFrozen(media));
71
+ });
72
+ await t.step("parses the example of FEP-ef61", () => {
73
+ const media = assertMedia(mediaUrl(`hl:${FEP_DIGEST}`));
74
+ assertEquals(media.digestMultibase, FEP_DIGEST);
75
+ assertEquals(media.digest.length, 32);
76
+ });
77
+ await t.step("normalizes the scheme and percent-encoding", () => {
78
+ for (const hashlink of [`HL:${mediaDigest}`, `hl%3a${mediaDigest}`]) {
79
+ const media = assertMedia(mediaUrl(hashlink));
80
+ assertEquals(media.hashlink, `hl:${mediaDigest}`);
81
+ assertEquals(media.digestMultibase, mediaDigest);
82
+ }
83
+ });
84
+ await t.step("accepts slashes in a base64 digest", () => {
85
+ assert(ffBase64.includes("/"));
86
+ for (const hashlink of [`hl:${ffBase64}`, `hl:${ffBase64.replaceAll("/", "%2F")}`]) {
87
+ const media = assertMedia(mediaUrl(hashlink));
88
+ assertEquals(media.digestMultibase, ffBase64);
89
+ assertEquals(media.digest, ffDigest);
90
+ assertEquals(media.multihash, ffMultihash);
91
+ }
92
+ });
93
+ await t.step("keeps the requested multibase encoding", () => {
94
+ const base58 = assertMedia(mediaUrl(`hl:${ffBase58}`));
95
+ const base64 = assertMedia(mediaUrl(`hl:${ffBase64}`));
96
+ assertEquals(base58.digestMultibase, ffBase58);
97
+ assertEquals(base64.digestMultibase, ffBase64);
98
+ assertEquals(base58.digest, base64.digest);
99
+ assertEquals(base58.multihash, base64.multihash);
100
+ });
101
+ await t.step("ignores the query", () => {
102
+ assertEquals(assertMedia(mediaUrl(`hl:${mediaDigest}?v=1`)).digestMultibase, mediaDigest);
103
+ });
104
+ await t.step("returns fresh digest bytes", () => {
105
+ const a = assertMedia(mediaUrl(`hl:${mediaDigest}`));
106
+ const b = assertMedia(mediaUrl(`hl:${mediaDigest}`));
107
+ a.digest.fill(0);
108
+ a.multihash.fill(0);
109
+ assertEquals(b.digest, mediaSha256);
110
+ });
111
+ await t.step("ignores other URLs", async () => {
112
+ const did = await exportDidKey(ed25519PublicKey.publicKey);
113
+ for (const url of [
114
+ "https://example.com/.well-known/apgateway",
115
+ "https://example.com/.well-known/apgateway/",
116
+ `https://example.com/.well-known/apgateway/${did}/notes/1`,
117
+ `https://example.com/.WELL-KNOWN/apgateway/hl:${mediaDigest}`,
118
+ `https://example.com/.well-known/apgateway-media/hl:${mediaDigest}`,
119
+ `https://example.com/hl:${mediaDigest}`,
120
+ `https://example.com/.well-known/apgateway/x/hl:${mediaDigest}`,
121
+ `https://example.com/.well-known/apgateway/hlx${mediaDigest}`
122
+ ]) assertStrictEquals(parse(url), null, url);
123
+ });
124
+ await t.step("recognizes malformed hashlinks", () => {
125
+ const sha512 = textDecoder.decode(encodeMultibase("base58btc", new Uint8Array([
126
+ 19,
127
+ 64,
128
+ .../* @__PURE__ */ new Uint8Array(64)
129
+ ])));
130
+ const short = textDecoder.decode(encodeMultibase("base58btc", new Uint8Array([
131
+ 18,
132
+ 16,
133
+ .../* @__PURE__ */ new Uint8Array(16)
134
+ ])));
135
+ for (const hashlink of [
136
+ "hl:",
137
+ "hl:z",
138
+ "hl:zInvalid0OIl",
139
+ `hl:${sha512}`,
140
+ `hl:${short}`,
141
+ `hl:${mediaDigest}:zCwPSdabLuj3jue1qYujzunnKwpL4myKdyeqySyFhnzZ8qdfW3bb6W8dVdRu`,
142
+ `hl:${mediaDigest}/`,
143
+ `hl:${mediaDigest}/extra`,
144
+ `hl:${mediaDigest}%E0%A4%A`,
145
+ `hl:${mediaDigest}%0A`
146
+ ]) assertMalformed(mediaUrl(hashlink));
147
+ });
148
+ });
149
+ test("Federation.fetch() serves hashlink media", async (t) => {
150
+ await t.step("passes the parsed hashlink to the dispatcher", async () => {
151
+ const federation = createTestFederation();
152
+ const calls = [];
153
+ federation.setHashlinkMediaDispatcher((ctx, media) => {
154
+ calls.push([ctx, media]);
155
+ return new Response(mediaBytes, { headers: { "Content-Type": "text/plain" } });
156
+ });
157
+ const response = await federation.fetch(new Request(mediaUrl(`hl:${mediaDigest}?download=1`)), { contextData: void 0 });
158
+ assertEquals(response.status, 200);
159
+ assertEquals(response.headers.get("Content-Type"), "text/plain");
160
+ assertEquals(new Uint8Array(await response.arrayBuffer()), mediaBytes);
161
+ assertEquals(calls.length, 1);
162
+ const [ctx, media] = calls[0];
163
+ assertEquals(ctx.request.url, mediaUrl(`hl:${mediaDigest}?download=1`));
164
+ assertEquals(ctx.url.href, mediaUrl(`hl:${mediaDigest}?download=1`));
165
+ assertEquals(media.hashlink, `hl:${mediaDigest}`);
166
+ assertEquals(media.digestMultibase, mediaDigest);
167
+ assertEquals(media.algorithm, "sha2-256");
168
+ assertEquals(media.digest, mediaSha256);
169
+ });
170
+ await t.step("sends the response of the dispatcher as is", async () => {
171
+ const federation = createTestFederation();
172
+ let pulled = 0;
173
+ let dispatched;
174
+ federation.setHashlinkMediaDispatcher(() => {
175
+ const body = new ReadableStream({ pull(controller) {
176
+ pulled++;
177
+ controller.enqueue(mediaBytes.subarray(0, 4));
178
+ } }, { highWaterMark: 0 });
179
+ dispatched = new Response(body, {
180
+ status: 206,
181
+ statusText: "Partial Content",
182
+ headers: {
183
+ "Content-Type": "image/png",
184
+ "Content-Range": "bytes 0-3/22",
185
+ "Cache-Control": "public, max-age=31536000, immutable",
186
+ "Content-Disposition": "attachment",
187
+ ETag: `"${mediaDigest}"`,
188
+ "Accept-Ranges": "bytes"
189
+ }
190
+ });
191
+ return dispatched;
192
+ });
193
+ const response = await federation.fetch(new Request(mediaUrl(`hl:${mediaDigest}`), { headers: { Range: "bytes=0-3" } }), { contextData: void 0 });
194
+ assertStrictEquals(response, dispatched);
195
+ assertEquals(pulled, 0);
196
+ assertEquals(response.status, 206);
197
+ assertEquals(response.statusText, "Partial Content");
198
+ assertEquals(response.headers.get("Content-Range"), "bytes 0-3/22");
199
+ assertEquals(response.headers.get("Cache-Control"), "public, max-age=31536000, immutable");
200
+ assertEquals(response.headers.get("Content-Disposition"), "attachment");
201
+ assertEquals(response.headers.get("ETag"), `"${mediaDigest}"`);
202
+ const reader = response.body.getReader();
203
+ assertEquals((await reader.read()).value, mediaBytes.subarray(0, 4));
204
+ await reader.cancel();
205
+ });
206
+ await t.step("does not touch the Vary header", async () => {
207
+ const federation = createTestFederation();
208
+ federation.setHashlinkMediaDispatcher(() => new Response(mediaBytes, { headers: { Vary: "Accept-Encoding" } }));
209
+ assertEquals((await federation.fetch(new Request(mediaUrl(`hl:${mediaDigest}`), { headers: { Accept: "application/activity+json" } }), { contextData: void 0 })).headers.get("Vary"), "Accept-Encoding");
210
+ });
211
+ await t.step("sends responses with immutable headers", async () => {
212
+ const federation = createTestFederation();
213
+ federation.setHashlinkMediaDispatcher(() => Response.redirect("https://cdn.example/media.png", 302));
214
+ const response = await federation.fetch(new Request(mediaUrl(`hl:${mediaDigest}`), { headers: { Accept: "application/activity+json" } }), { contextData: void 0 });
215
+ assertEquals(response.status, 302);
216
+ assertEquals(response.headers.get("Location"), "https://cdn.example/media.png");
217
+ });
218
+ await t.step("responds with 404 if the dispatcher returns null", async () => {
219
+ const federation = createTestFederation();
220
+ federation.setHashlinkMediaDispatcher(() => null);
221
+ assertEquals((await federation.fetch(new Request(mediaUrl(`hl:${mediaDigest}`)), { contextData: void 0 })).status, 404);
222
+ const custom = await federation.fetch(new Request(mediaUrl(`hl:${mediaDigest}`)), {
223
+ contextData: void 0,
224
+ onNotFound: () => new Response("Nope.", { status: 404 })
225
+ });
226
+ assertEquals(custom.status, 404);
227
+ assertEquals(await custom.text(), "Nope.");
228
+ });
229
+ await t.step("responds with 400 to malformed hashlinks", async () => {
230
+ const federation = createTestFederation();
231
+ let called = false;
232
+ federation.setHashlinkMediaDispatcher(() => {
233
+ called = true;
234
+ return new Response(mediaBytes);
235
+ });
236
+ for (const hashlink of [
237
+ "hl:zInvalid",
238
+ `hl:${mediaDigest}/`,
239
+ `hl:${mediaDigest}:z123`
240
+ ]) {
241
+ const response = await federation.fetch(new Request(mediaUrl(hashlink)), { contextData: void 0 });
242
+ assertEquals(response.status, 400, hashlink);
243
+ assertEquals(response.headers.get("Content-Type"), "text/plain; charset=utf-8");
244
+ }
245
+ assertFalse(called);
246
+ });
247
+ await t.step("responds with 405 to other methods", async () => {
248
+ const federation = createTestFederation();
249
+ let called = false;
250
+ federation.setHashlinkMediaDispatcher(() => {
251
+ called = true;
252
+ return new Response(mediaBytes);
253
+ });
254
+ for (const hashlink of [`hl:${mediaDigest}`, "hl:zInvalid"]) {
255
+ const response = await federation.fetch(new Request(mediaUrl(hashlink), {
256
+ method: "POST",
257
+ body: mediaBytes
258
+ }), { contextData: void 0 });
259
+ assertEquals(response.status, 405);
260
+ assertEquals(response.headers.get("Allow"), "GET, HEAD");
261
+ }
262
+ assertFalse(called);
263
+ });
264
+ await t.step("responds to HEAD requests without bodies", async () => {
265
+ const federation = createTestFederation();
266
+ let cancelled = false;
267
+ let method;
268
+ federation.setHashlinkMediaDispatcher((ctx) => {
269
+ method = ctx.request.method;
270
+ const body = new ReadableStream({
271
+ start(controller) {
272
+ controller.enqueue(mediaBytes);
273
+ },
274
+ cancel() {
275
+ cancelled = true;
276
+ }
277
+ });
278
+ return new Response(body, { headers: {
279
+ "Content-Type": "image/png",
280
+ "Content-Length": mediaBytes.length.toString()
281
+ } });
282
+ });
283
+ const response = await federation.fetch(new Request(mediaUrl(`hl:${mediaDigest}`), { method: "HEAD" }), { contextData: void 0 });
284
+ assertEquals(method, "HEAD");
285
+ assertEquals(response.status, 200);
286
+ assertStrictEquals(response.body, null);
287
+ assert(cancelled);
288
+ assertEquals(response.headers.get("Content-Type"), "image/png");
289
+ assertEquals(response.headers.get("Content-Length"), mediaBytes.length.toString());
290
+ const malformed = await federation.fetch(new Request(mediaUrl("hl:zInvalid"), { method: "HEAD" }), { contextData: void 0 });
291
+ assertEquals(malformed.status, 400);
292
+ assertStrictEquals(malformed.body, null);
293
+ });
294
+ await t.step("does not wait for cancelling a cloned body", async () => {
295
+ const federation = createTestFederation();
296
+ const cached = new Response(mediaBytes, { headers: { "Content-Type": "image/png" } });
297
+ federation.setHashlinkMediaDispatcher(() => cached.clone());
298
+ let timer;
299
+ const response = await Promise.race([federation.fetch(new Request(mediaUrl(`hl:${mediaDigest}`), { method: "HEAD" }), { contextData: void 0 }), new Promise((resolve) => {
300
+ timer = setTimeout(() => resolve(null), 1e3);
301
+ })]);
302
+ clearTimeout(timer);
303
+ assert(response != null, "the HEAD response should not hang");
304
+ assertEquals(response.status, 200);
305
+ assertStrictEquals(response.body, null);
306
+ assertEquals(response.headers.get("Content-Type"), "image/png");
307
+ await cached.body.cancel();
308
+ });
309
+ await t.step("returns onNotFound responses as is", async () => {
310
+ const federation = createTestFederation();
311
+ federation.setHashlinkMediaDispatcher(() => null);
312
+ const notFound = new Response("", { status: 404 });
313
+ for (const method of ["GET", "HEAD"]) assertStrictEquals(await federation.fetch(new Request(mediaUrl(`hl:${mediaDigest}`), { method }), {
314
+ contextData: void 0,
315
+ onNotFound: () => notFound
316
+ }), notFound, method);
317
+ });
318
+ await t.step("propagates errors thrown by the dispatcher", async () => {
319
+ const federation = createTestFederation();
320
+ const error = /* @__PURE__ */ new Error("Storage is down.");
321
+ federation.setHashlinkMediaDispatcher(() => {
322
+ throw error;
323
+ });
324
+ let thrown;
325
+ try {
326
+ await federation.fetch(new Request(mediaUrl(`hl:${mediaDigest}`)), { contextData: void 0 });
327
+ } catch (e) {
328
+ thrown = e;
329
+ }
330
+ assertStrictEquals(thrown, error);
331
+ });
332
+ await t.step("records metrics", async () => {
333
+ const [meterProvider, recorder] = createTestMeterProvider();
334
+ const federation = createFederation({
335
+ kv: new MemoryKvStore(),
336
+ documentLoaderFactory: () => mockDocumentLoader,
337
+ contextLoaderFactory: () => mockDocumentLoader,
338
+ meterProvider
339
+ });
340
+ federation.setHashlinkMediaDispatcher(() => new Response(mediaBytes));
341
+ await federation.fetch(new Request(mediaUrl(`hl:${mediaDigest}`)), { contextData: void 0 });
342
+ await federation.fetch(new Request(mediaUrl("hl:zInvalid")), { contextData: void 0 });
343
+ const counts = recorder.getMeasurements("fedify.http.server.request.count");
344
+ assertEquals(counts.length, 2);
345
+ for (const [count, status] of [[counts[0], 200], [counts[1], 400]]) {
346
+ assertEquals(count.attributes["fedify.endpoint"], "hashlink_media");
347
+ assertEquals(count.attributes["http.response.status_code"], status);
348
+ assertEquals(count.attributes["fedify.route.template"], "/.well-known/apgateway/hl:{digestMultibase}");
349
+ }
350
+ });
351
+ });
352
+ test("Federation.fetch() routes hashlink media apart from other routes", async (t) => {
353
+ const did = await exportDidKey(ed25519PublicKey.publicKey);
354
+ await t.step("responds with 404 without a dispatcher", async () => {
355
+ const federation = createTestFederation();
356
+ for (const method of ["GET", "POST"]) assertEquals((await federation.fetch(new Request(mediaUrl(`hl:${mediaDigest}`), { method }), { contextData: void 0 })).status, 404, method);
357
+ assertEquals((await federation.fetch(new Request(mediaUrl("hl:zInvalid")), { contextData: void 0 })).status, 404);
358
+ });
359
+ await t.step("keeps portable objects apart from media", async () => {
360
+ const federation = createTestFederation();
361
+ const objectCalls = [];
362
+ let mediaCalled = false;
363
+ federation.setObjectDispatcher(Note, "/notes/{id}", (_ctx, values) => {
364
+ objectCalls.push(values);
365
+ return null;
366
+ });
367
+ federation.setHashlinkMediaDispatcher(() => {
368
+ mediaCalled = true;
369
+ return null;
370
+ });
371
+ assertEquals((await federation.fetch(new Request(`https://example.com/.well-known/apgateway/${did}/notes/1`, { headers: { Accept: "application/activity+json" } }), { contextData: void 0 })).status, 404);
372
+ assertEquals(objectCalls, [{ id: "1" }]);
373
+ assertFalse(mediaCalled);
374
+ await federation.fetch(new Request(mediaUrl(`hl:${mediaDigest}`), { headers: { Accept: "application/activity+json" } }), { contextData: void 0 });
375
+ assert(mediaCalled);
376
+ assertEquals(objectCalls.length, 1);
377
+ });
378
+ await t.step("lets application routes shadow the gateway", async () => {
379
+ const federation = createTestFederation();
380
+ const objectCalls = [];
381
+ let mediaCalled = false;
382
+ federation.setObjectDispatcher(Note, "/.well-known/apgateway/{+id}", (_ctx, values) => {
383
+ objectCalls.push(values);
384
+ return null;
385
+ });
386
+ federation.setHashlinkMediaDispatcher(() => {
387
+ mediaCalled = true;
388
+ return new Response(mediaBytes);
389
+ });
390
+ assertEquals((await federation.fetch(new Request(mediaUrl(`hl:${mediaDigest}`), { headers: { Accept: "application/activity+json" } }), { contextData: void 0 })).status, 404);
391
+ assertEquals(objectCalls, [{ id: `hl:${mediaDigest}` }]);
392
+ assertFalse(mediaCalled);
393
+ });
394
+ await t.step("keeps unrelated paths not found", async () => {
395
+ const federation = createTestFederation();
396
+ let mediaCalled = false;
397
+ federation.setHashlinkMediaDispatcher(() => {
398
+ mediaCalled = true;
399
+ return new Response(mediaBytes);
400
+ });
401
+ for (const url of [
402
+ `https://example.com/hl:${mediaDigest}`,
403
+ "https://example.com/.well-known/apgateway",
404
+ `https://example.com/.well-known/apgateway-media/hl:${mediaDigest}`
405
+ ]) assertEquals((await federation.fetch(new Request(url), { contextData: void 0 })).status, 404, url);
406
+ assertFalse(mediaCalled);
407
+ });
408
+ });
409
+ test("FederationBuilder.setHashlinkMediaDispatcher()", async () => {
410
+ const builder = createFederationBuilder();
411
+ builder.setHashlinkMediaDispatcher(() => new Response(mediaBytes));
412
+ assertThrows(() => builder.setHashlinkMediaDispatcher(() => null), RouterError, "Hashlink media dispatcher already set.");
413
+ const response = await (await builder.build({
414
+ kv: new MemoryKvStore(),
415
+ documentLoaderFactory: () => mockDocumentLoader,
416
+ contextLoaderFactory: () => mockDocumentLoader
417
+ })).fetch(new Request(mediaUrl(`hl:${mediaDigest}`)), { contextData: void 0 });
418
+ assertEquals(response.status, 200);
419
+ assertEquals(new Uint8Array(await response.arrayBuffer()), mediaBytes);
420
+ });
421
+ //#endregion
422
+ export {};
@@ -1,12 +1,12 @@
1
1
  import "temporal-polyfill";
2
2
  import "urlpattern-polyfill";
3
3
  globalThis.addEventListener = () => {};
4
- import { t as MemoryKvStore } from "../kv-CXBAok3k.mjs";
5
- import { t as assertEquals } from "../assert_equals-BV7oLMxm.mjs";
6
- import "../std__assert-B2NnY9bq.mjs";
7
- import { n as ed25519PrivateKey, r as ed25519PublicKey, t as ed25519Multikey } from "../keys-Dbyd7bhl.mjs";
8
- import { r as signObject } from "../proof-B4ngoaNQ.mjs";
9
- import { o as createFederation } from "../middleware-CvwTUgnP.mjs";
4
+ import { t as MemoryKvStore } from "../kv-DCHkaJ9-.mjs";
5
+ import { t as assertEquals } from "../assert_equals-Z6IcEbWU.mjs";
6
+ import "../std__assert-DvMDRPes.mjs";
7
+ import { a as signObject } from "../proof-Dxy6ByqI.mjs";
8
+ import { n as ed25519PrivateKey, r as ed25519PublicKey, t as ed25519Multikey } from "../keys-DIJNHW-Y.mjs";
9
+ import { o as createFederation } from "../middleware-D7_LO-Vs.mjs";
10
10
  import { Create, Follow, Person } from "@fedify/vocab";
11
11
  import { mockDocumentLoader, test } from "@fedify/fixture";
12
12
  //#region src/federation/idempotency.test.ts
@@ -0,0 +1,2 @@
1
+ import { Temporal } from "temporal-polyfill";
2
+ import { URLPattern } from "urlpattern-polyfill";