@fedify/fedify 2.1.30 → 2.1.31

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 (84) hide show
  1. package/dist/{builder-CTpfuuVp.mjs → builder-isMRh_I6.mjs} +3 -3
  2. package/dist/{client-CAM_bQXx.d.cts → client-BIBzqEIp.d.cts} +0 -1
  3. package/dist/{client-CjDMsn4a.mjs → client-BZr7TGyu.mjs} +113 -64
  4. package/dist/{client-CSddvgWN.d.ts → client-jE7oGvRh.d.ts} +0 -1
  5. package/dist/compat/mod.d.cts +1 -1
  6. package/dist/compat/mod.d.ts +1 -1
  7. package/dist/compat/transformers.test.mjs +1 -1
  8. package/dist/{context-DLLaDau8.d.ts → context-C0uGVzgk.d.ts} +28 -2
  9. package/dist/{context-BNwv9Q1v.d.cts → context-CLaLxi49.d.cts} +28 -2
  10. package/dist/{deno-BATJ1hq6.mjs → deno-CiaJqTcu.mjs} +1 -1
  11. package/dist/docloader-DOrwedT7.mjs +124 -0
  12. package/dist/federation/builder.test.mjs +1 -1
  13. package/dist/federation/handler.test.mjs +6 -6
  14. package/dist/federation/idempotency.test.mjs +2 -2
  15. package/dist/federation/inbox.test.mjs +1 -1
  16. package/dist/federation/middleware.test.mjs +380 -24
  17. package/dist/federation/mod.cjs +1 -1
  18. package/dist/federation/mod.d.cts +2 -2
  19. package/dist/federation/mod.d.ts +2 -2
  20. package/dist/federation/mod.js +1 -1
  21. package/dist/federation/send.test.mjs +445 -70
  22. package/dist/federation/temporal.test.mjs +1 -1
  23. package/dist/federation/webfinger.test.mjs +1 -1
  24. package/dist/{http-Dl00HXr4.d.ts → http-9IFUxdkp.d.cts} +1 -2
  25. package/dist/{http-Bjd6I92u.mjs → http-A9mP2uM7.mjs} +194 -96
  26. package/dist/{http-BoR1M3W7.cjs → http-CRiVlZd5.cjs} +172 -103
  27. package/dist/{http-CK3efjgM.js → http-Crml3gei.js} +171 -102
  28. package/dist/{http-DqZhyEso.d.cts → http-SbfezERn.d.ts} +1 -2
  29. package/dist/{inbox-9MlBGgs9.mjs → inbox-KESLudq4.mjs} +1 -1
  30. package/dist/{key-ClQh9Qqh.mjs → key-Dg6-DV_O.mjs} +1 -1
  31. package/dist/{kv-cache-BxSx_Iyp.cjs → kv-cache-Cqmm-yQa.cjs} +59 -17
  32. package/dist/{kv-cache-ImApO425.js → kv-cache-DQcyZL5T.js} +60 -18
  33. package/dist/{ld-Crg0Xmwr.mjs → ld-COjDN6Nb.mjs} +2 -2
  34. package/dist/{middleware-CmD3lpy4.cjs → middleware-BbgR7xOI.cjs} +184 -59
  35. package/dist/{middleware-jL9p9V9G.cjs → middleware-BtDQUCcz.cjs} +1 -1
  36. package/dist/{middleware-CE0b69gj.mjs → middleware-COFlKeYn.mjs} +78 -38
  37. package/dist/{middleware-Bi5D6DBT.mjs → middleware-CuQvczZd.mjs} +1 -1
  38. package/dist/{middleware-C2AJFk8W.js → middleware-DIW5S_5L.js} +184 -59
  39. package/dist/{mod-DNEgTI8M.d.ts → mod-BRnwh2oy.d.ts} +2 -7
  40. package/dist/{mod-BLDHhuW0.d.ts → mod-C7w-QVUv.d.ts} +1 -1
  41. package/dist/{mod-CWJNbyju.d.ts → mod-CFRocFeQ.d.ts} +1 -1
  42. package/dist/{mod-BmlfRBmU.d.cts → mod-CGUH8Ujp.d.cts} +1 -1
  43. package/dist/{mod-Cn9GJ5ex.d.cts → mod-Co7LEK9X.d.cts} +2 -7
  44. package/dist/{mod-DiuFTYUt.d.cts → mod-LOEOKbYd.d.cts} +1 -1
  45. package/dist/mod.cjs +5 -5
  46. package/dist/mod.d.cts +6 -6
  47. package/dist/mod.d.ts +6 -6
  48. package/dist/mod.js +5 -5
  49. package/dist/nodeinfo/client.test.mjs +337 -19
  50. package/dist/nodeinfo/handler.test.mjs +1 -1
  51. package/dist/nodeinfo/mod.cjs +1 -1
  52. package/dist/nodeinfo/mod.d.cts +1 -1
  53. package/dist/nodeinfo/mod.d.ts +1 -1
  54. package/dist/nodeinfo/mod.js +1 -1
  55. package/dist/otel/exporter.test.mjs +292 -30
  56. package/dist/otel/mod.cjs +36 -30
  57. package/dist/otel/mod.d.cts +13 -0
  58. package/dist/otel/mod.d.ts +13 -0
  59. package/dist/otel/mod.js +36 -30
  60. package/dist/{owner-BRoKojK1.mjs → owner-CFHq61cq.mjs} +2 -2
  61. package/dist/{proof-CHXVlsH_.cjs → proof-0JkSC_pT.cjs} +1 -1
  62. package/dist/{proof-7R4s10TJ.js → proof-DzksJFK7.js} +1 -1
  63. package/dist/{proof-DMIAezxz.mjs → proof-KSHlbPCG.mjs} +2 -2
  64. package/dist/{send-BHIt_8ia.mjs → send-DaDe2YCb.mjs} +117 -32
  65. package/dist/sig/http.test.mjs +506 -2
  66. package/dist/sig/key.test.mjs +2 -2
  67. package/dist/sig/ld.test.mjs +2 -2
  68. package/dist/sig/mod.cjs +2 -2
  69. package/dist/sig/mod.d.cts +2 -2
  70. package/dist/sig/mod.d.ts +2 -2
  71. package/dist/sig/mod.js +2 -2
  72. package/dist/sig/owner.test.mjs +1 -1
  73. package/dist/sig/proof.test.mjs +1 -1
  74. package/dist/{temporal-DR0JahMu.mjs → temporal-DNGH8o2E.mjs} +1 -1
  75. package/dist/testing/mod.d.mts +26 -1
  76. package/dist/{types-miAYfiGO.js → types-B1_pRFE_.js} +112 -63
  77. package/dist/{types-DRPjmHVV.cjs → types-DOvyymM4.cjs} +111 -62
  78. package/dist/utils/docloader.test.mjs +316 -32
  79. package/dist/utils/mod.cjs +1 -1
  80. package/dist/utils/mod.d.cts +1 -1
  81. package/dist/utils/mod.d.ts +1 -1
  82. package/dist/utils/mod.js +1 -1
  83. package/package.json +6 -6
  84. package/dist/docloader-OLWc7P2D.mjs +0 -82
@@ -1,10 +1,11 @@
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-BATJ1hq6.mjs";
5
- import { n as doubleKnock } from "./http-Bjd6I92u.mjs";
4
+ import { n as version, t as name } from "./deno-CiaJqTcu.mjs";
5
+ import { r as doubleKnockScoped } from "./http-A9mP2uM7.mjs";
6
6
  import { SpanKind, SpanStatusCode, trace } from "@opentelemetry/api";
7
- import { FetchError, UrlError, validatePublicUrl } from "@fedify/vocab-runtime";
7
+ import { FetchError, UrlError } from "@fedify/vocab-runtime";
8
+ import { openDirectFetch, openPublicFetch } from "@fedify/vocab-runtime/internal";
8
9
  import { getLogger } from "@logtape/logtape";
9
10
  //#region src/federation/send.ts
10
11
  /**
@@ -38,9 +39,10 @@ function extractInboxes({ recipients, preferSharedInbox, excludeBaseUris }) {
38
39
  *
39
40
  * @param parameters The parameters for sending the activity.
40
41
  * See also {@link SendActivityParameters}.
42
+ * @param deps The dependencies, for tests.
41
43
  * @throws {Error} If the activity fails to send.
42
44
  */
43
- function sendActivity(options) {
45
+ function sendActivity(options, deps = {}) {
44
46
  const tracerProvider = options.tracerProvider ?? trace.getTracerProvider();
45
47
  return tracerProvider.getTracer(name, version).startActiveSpan("activitypub.send_activity", {
46
48
  kind: SpanKind.CLIENT,
@@ -52,7 +54,7 @@ function sendActivity(options) {
52
54
  await sendActivityInternal({
53
55
  ...options,
54
56
  tracerProvider
55
- }, span);
57
+ }, span, deps);
56
58
  } catch (e) {
57
59
  span.setStatus({
58
60
  code: SpanStatusCode.ERROR,
@@ -86,21 +88,25 @@ async function readLimitedResponseBody(response, maxBytes) {
86
88
  totalBytes += value.length;
87
89
  }
88
90
  } finally {
91
+ if (truncated) reader.cancel().catch(() => {});
89
92
  reader.releaseLock();
90
93
  }
91
94
  let result = chunks.join("");
92
95
  if (truncated) result += "… (truncated)";
93
96
  return result;
94
97
  }
95
- async function sendActivityInternal({ activity, activityId, keys, inbox, allowPrivateAddress, headers, specDeterminer, tracerProvider }, span) {
98
+ async function sendActivityInternal({ activity, activityId, keys, inbox, allowPrivateAddress, headers, specDeterminer, tracerProvider }, span, deps) {
96
99
  const logger = getLogger([
97
100
  "fedify",
98
101
  "federation",
99
102
  "outbox"
100
103
  ]);
101
- async function validateUrl(url) {
102
- if (!allowPrivateAddress) try {
103
- await validatePublicUrl(url);
104
+ const openUnvalidatedHop = deps.openHop ?? (allowPrivateAddress ? openDirectFetch : (url) => openPublicFetch(url));
105
+ let opened = false;
106
+ async function openHop(url) {
107
+ let session;
108
+ try {
109
+ session = await openUnvalidatedHop(url);
104
110
  } catch (error) {
105
111
  if (error instanceof UrlError && error.reason === "dns") {
106
112
  logger.error("DNS resolution failed for URL: {url}", {
@@ -117,8 +123,9 @@ async function sendActivityInternal({ activity, activityId, keys, inbox, allowPr
117
123
  });
118
124
  throw error;
119
125
  }
126
+ opened = true;
127
+ return session;
120
128
  }
121
- await validateUrl(inbox.href);
122
129
  headers = new Headers(headers);
123
130
  headers.set("Content-Type", "application/activity+json");
124
131
  const request = new Request(inbox, {
@@ -139,36 +146,30 @@ async function sendActivityInternal({ activity, activityId, keys, inbox, allowPr
139
146
  privateKey: pair.privateKey
140
147
  }))
141
148
  });
142
- let response;
149
+ let outcome;
143
150
  try {
144
- response = rsaKey == null ? await fetchWithValidatedRedirects(request, validateUrl) : await doubleKnock(request, rsaKey, {
151
+ outcome = rsaKey == null ? await fetchWithValidatedRedirects(request, openHop, (response) => consumeSendActivityResponse(response, inbox)) : await doubleKnockScoped(request, rsaKey, {
145
152
  tracerProvider,
146
- specDeterminer,
147
- validateRedirect: validateUrl
148
- });
153
+ specDeterminer
154
+ }, openHop, (response) => consumeSendActivityResponse(response, inbox));
149
155
  } catch (error) {
150
- logger.error("Failed to send activity {activityId} to {inbox}:\n{error}", {
156
+ if (opened) logger.error("Failed to send activity {activityId} to {inbox}:\n{error}", {
151
157
  activityId,
152
158
  inbox: inbox.href,
153
159
  error
154
160
  });
155
161
  throw error;
156
162
  }
157
- if (!response.ok) {
158
- let error;
159
- try {
160
- error = await readLimitedResponseBody(response, MAX_ERROR_RESPONSE_BODY_BYTES);
161
- } catch (_) {
162
- error = "";
163
- }
163
+ if (!outcome.ok) {
164
+ const { status, statusText, error } = outcome;
164
165
  logger.error("Failed to send activity {activityId} to {inbox} ({status} {statusText}):\n{error}", {
165
166
  activityId,
166
167
  inbox: inbox.href,
167
- status: response.status,
168
- statusText: response.statusText,
168
+ status,
169
+ statusText,
169
170
  error
170
171
  });
171
- throw new SendActivityError(inbox, response.status, `Failed to send activity ${activityId} to ${inbox.href} (${response.status} ${response.statusText}):\n${error}`, error);
172
+ throw new SendActivityError(inbox, status, `Failed to send activity ${activityId} to ${inbox.href} (${status} ${statusText}):\n${error}`, error);
172
173
  }
173
174
  span.addEvent("activitypub.activity.sent", {
174
175
  "activitypub.activity.json": JSON.stringify(activity),
@@ -176,10 +177,74 @@ async function sendActivityInternal({ activity, activityId, keys, inbox, allowPr
176
177
  "activitypub.activity.id": activityId ?? ""
177
178
  });
178
179
  }
179
- async function fetchWithValidatedRedirects(request, validateUrl) {
180
+ /**
181
+ * Starts cancelling the body of a response without waiting for it to settle.
182
+ * The cancellation of a branch of a tee (e.g., a body cloned by a fetch mock)
183
+ * settles only after the other branch is cancelled too, per the Streams
184
+ * standard, so awaiting it could block forever. Failures are only logged.
185
+ */
186
+ function cancelResponseBody(response, url) {
187
+ response.body?.cancel().catch((error) => {
188
+ getLogger([
189
+ "fedify",
190
+ "federation",
191
+ "outbox"
192
+ ]).debug("Failed to cancel the response body from {url}: {error}", {
193
+ url,
194
+ error
195
+ });
196
+ });
197
+ }
198
+ /**
199
+ * Reads what is needed from the final response of a delivery while its
200
+ * session is open: the body of a successful response is cancelled, and at
201
+ * most 1 KiB of an error response body is read.
202
+ */
203
+ async function consumeSendActivityResponse(response, inbox) {
204
+ if (response.ok) {
205
+ cancelResponseBody(response, response.url || inbox.href);
206
+ return { ok: true };
207
+ }
208
+ let error;
209
+ try {
210
+ error = await readLimitedResponseBody(response, MAX_ERROR_RESPONSE_BODY_BYTES);
211
+ } catch (_) {
212
+ error = "";
213
+ }
214
+ return {
215
+ ok: false,
216
+ status: response.status,
217
+ statusText: response.statusText,
218
+ error
219
+ };
220
+ }
221
+ /**
222
+ * Sends a request without signing it, following redirects manually like the
223
+ * Fetch standard does. Every hop is opened with `openHop()`, which validates
224
+ * the destination, after the previous hop is closed. The final response is
225
+ * passed to `consume()`, and its session is closed after that.
226
+ * This is exported only for tests; it is not exported from any entry point.
227
+ * @param request The request to send.
228
+ * @param openHop The function to open a session for each hop.
229
+ * @param consume The function to consume the final response.
230
+ * @returns The value returned by `consume`.
231
+ */
232
+ async function fetchWithValidatedRedirects(request, openHop, consume) {
233
+ const logger = getLogger([
234
+ "fedify",
235
+ "federation",
236
+ "outbox"
237
+ ]);
180
238
  let body = await request.clone().text();
181
239
  for (let redirects = 0;; redirects++) {
182
- const response = await fetch(request, { redirect: "manual" });
240
+ const session = await openHop(request.url);
241
+ let response;
242
+ try {
243
+ response = await session.fetch(request, { redirect: "manual" });
244
+ } catch (error) {
245
+ await closeAfterError(session, logger);
246
+ throw error;
247
+ }
183
248
  const location = response.headers.get("Location");
184
249
  if (![
185
250
  301,
@@ -187,11 +252,21 @@ async function fetchWithValidatedRedirects(request, validateUrl) {
187
252
  303,
188
253
  307,
189
254
  308
190
- ].includes(response.status) || location == null) return response;
191
- await response.body?.cancel();
255
+ ].includes(response.status) || location == null) try {
256
+ return await consume(response);
257
+ } catch (error) {
258
+ logger.debug("Failed to consume the response from {url}; closing the session: {error}", {
259
+ url: request.url,
260
+ error
261
+ });
262
+ throw error;
263
+ } finally {
264
+ await session.close();
265
+ }
266
+ cancelResponseBody(response, request.url);
267
+ await session.close();
192
268
  if (redirects >= 20) throw new TypeError("Too many redirects");
193
269
  const url = new URL(location, request.url);
194
- await validateUrl(url.href);
195
270
  const headers = new Headers(request.headers);
196
271
  if (url.origin !== new URL(request.url).origin) {
197
272
  headers.delete("Authorization");
@@ -219,6 +294,16 @@ async function fetchWithValidatedRedirects(request, validateUrl) {
219
294
  });
220
295
  }
221
296
  }
297
+ async function closeAfterError(session, logger) {
298
+ try {
299
+ await session.close();
300
+ } catch (error) {
301
+ logger.debug("Failed to close the session for {url}: {error}", {
302
+ url: session.url.href,
303
+ error
304
+ });
305
+ }
306
+ }
222
307
  /**
223
308
  * An error that is thrown when an activity fails to send to a remote inbox.
224
309
  * It contains structured information about the failure, including the HTTP
@@ -257,4 +342,4 @@ var SendActivityError = class extends Error {
257
342
  }
258
343
  };
259
344
  //#endregion
260
- export { extractInboxes as n, sendActivity as r, SendActivityError as t };
345
+ export { sendActivity as i, extractInboxes as n, fetchWithValidatedRedirects as r, SendActivityError as t };