@fedify/adonisjs 2.4.0-dev.0

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 (82) hide show
  1. package/LICENSE +20 -0
  2. package/README.md +420 -0
  3. package/dist/configure.cjs +108 -0
  4. package/dist/configure.cjs.map +1 -0
  5. package/dist/configure.d.cts +14 -0
  6. package/dist/configure.d.ts +14 -0
  7. package/dist/configure.js +107 -0
  8. package/dist/configure.js.map +1 -0
  9. package/dist/index.cjs +19 -0
  10. package/dist/index.d.cts +8 -0
  11. package/dist/index.d.ts +8 -0
  12. package/dist/index.js +8 -0
  13. package/dist/package.cjs +121 -0
  14. package/dist/package.cjs.map +1 -0
  15. package/dist/package.js +115 -0
  16. package/dist/package.js.map +1 -0
  17. package/dist/providers/fedify_provider.cjs +270 -0
  18. package/dist/providers/fedify_provider.cjs.map +1 -0
  19. package/dist/providers/fedify_provider.d.cts +54 -0
  20. package/dist/providers/fedify_provider.d.ts +55 -0
  21. package/dist/providers/fedify_provider.js +269 -0
  22. package/dist/providers/fedify_provider.js.map +1 -0
  23. package/dist/services/builder.cjs +9 -0
  24. package/dist/services/builder.cjs.map +1 -0
  25. package/dist/services/builder.d.cts +7 -0
  26. package/dist/services/builder.d.ts +8 -0
  27. package/dist/services/builder.js +8 -0
  28. package/dist/services/builder.js.map +1 -0
  29. package/dist/src/builder.cjs +55 -0
  30. package/dist/src/builder.cjs.map +1 -0
  31. package/dist/src/builder.d.cts +14 -0
  32. package/dist/src/builder.d.ts +14 -0
  33. package/dist/src/builder.js +54 -0
  34. package/dist/src/builder.js.map +1 -0
  35. package/dist/src/define_config.cjs +114 -0
  36. package/dist/src/define_config.cjs.map +1 -0
  37. package/dist/src/define_config.d.cts +30 -0
  38. package/dist/src/define_config.d.ts +30 -0
  39. package/dist/src/define_config.js +113 -0
  40. package/dist/src/define_config.js.map +1 -0
  41. package/dist/src/errors.cjs +77 -0
  42. package/dist/src/errors.cjs.map +1 -0
  43. package/dist/src/errors.d.cts +56 -0
  44. package/dist/src/errors.d.ts +56 -0
  45. package/dist/src/errors.js +73 -0
  46. package/dist/src/errors.js.map +1 -0
  47. package/dist/src/fedify_middleware.cjs +507 -0
  48. package/dist/src/fedify_middleware.cjs.map +1 -0
  49. package/dist/src/fedify_middleware.d.cts +85 -0
  50. package/dist/src/fedify_middleware.d.ts +85 -0
  51. package/dist/src/fedify_middleware.js +505 -0
  52. package/dist/src/fedify_middleware.js.map +1 -0
  53. package/dist/src/http_context.cjs +79 -0
  54. package/dist/src/http_context.cjs.map +1 -0
  55. package/dist/src/http_context.d.cts +31 -0
  56. package/dist/src/http_context.d.ts +31 -0
  57. package/dist/src/http_context.js +76 -0
  58. package/dist/src/http_context.js.map +1 -0
  59. package/dist/src/logging.cjs +106 -0
  60. package/dist/src/logging.cjs.map +1 -0
  61. package/dist/src/logging.d.cts +45 -0
  62. package/dist/src/logging.d.ts +45 -0
  63. package/dist/src/logging.js +106 -0
  64. package/dist/src/logging.js.map +1 -0
  65. package/dist/src/types.cjs +1 -0
  66. package/dist/src/types.d.cts +269 -0
  67. package/dist/src/types.d.ts +269 -0
  68. package/dist/src/types.js +1 -0
  69. package/dist/src/unusable_authority.cjs +79 -0
  70. package/dist/src/unusable_authority.cjs.map +1 -0
  71. package/dist/src/unusable_authority.js +76 -0
  72. package/dist/src/unusable_authority.js.map +1 -0
  73. package/dist/stubs/config/fedify.stub +105 -0
  74. package/dist/stubs/federation/main.stub +64 -0
  75. package/dist/stubs/main.cjs +16 -0
  76. package/dist/stubs/main.cjs.map +1 -0
  77. package/dist/stubs/main.d.cts +11 -0
  78. package/dist/stubs/main.d.ts +11 -0
  79. package/dist/stubs/main.js +15 -0
  80. package/dist/stubs/main.js.map +1 -0
  81. package/dist/stubs/start/federation.stub +20 -0
  82. package/package.json +124 -0
@@ -0,0 +1,85 @@
1
+ import { ContextData, ContextDataFactory } from "./types.js";
2
+ import { HttpContext } from "@adonisjs/core/http";
3
+ import { Federation } from "@fedify/fedify";
4
+ import { NextFn } from "@adonisjs/core/types/http";
5
+
6
+ //#region src/fedify_middleware.d.ts
7
+ /**
8
+ * Options accepted by {@link FedifyMiddleware} and {@link fedifyMiddleware}.
9
+ */
10
+ interface FedifyMiddlewareOptions {
11
+ /**
12
+ * URL path prefixes to bypass entirely. See
13
+ * {@link import('./types.js').FedifyConfig.ignoreRoutePrefixes}.
14
+ *
15
+ * @default `[]`
16
+ */
17
+ ignoreRoutePrefixes?: string[];
18
+ }
19
+ /**
20
+ * The instance shape of the middleware.
21
+ */
22
+ interface FedifyMiddlewareHandler {
23
+ handle(ctx: HttpContext, next: NextFn): Promise<void>;
24
+ }
25
+ /**
26
+ * What {@link fedifyMiddleware} returns: a middleware **class**, since
27
+ * `server.use()` constructs entries through the container.
28
+ */
29
+ type FedifyMiddlewareClass = new () => FedifyMiddlewareHandler;
30
+ /**
31
+ * The arguments after `federation`. The factory defaults to producing
32
+ * `undefined`, so it may only be omitted when `TContextData` admits it;
33
+ * otherwise omitting it is a compile error. The tuple wrapper keeps the
34
+ * conditional from distributing over a union.
35
+ */
36
+ type FedifyMiddlewareArgs<TContextData> = [undefined] extends [TContextData] ? [contextDataFactory?: ContextDataFactory<TContextData>, options?: FedifyMiddlewareOptions] : [contextDataFactory: ContextDataFactory<TContextData>, options?: FedifyMiddlewareOptions];
37
+ /**
38
+ * Mounts a `Federation` instance inside an AdonisJS application; the
39
+ * counterpart of `integrateFederation()` in `@fedify/express`.
40
+ *
41
+ * Register it in the **server** stack: Fedify serves paths the router does
42
+ * not know, and the body stream is still intact there for signature
43
+ * verification. An instance holds configuration only, so one serves every
44
+ * request.
45
+ */
46
+ declare class FedifyMiddleware<TContextData = ContextData> implements FedifyMiddlewareHandler {
47
+ #private;
48
+ /**
49
+ * @param federation The federation instance to mount.
50
+ * @param args The context-data factory and the options; see
51
+ * {@link FedifyMiddlewareArgs}. The factory sees a pre-routing
52
+ * `HttpContext`; see {@link import('./types.js').ContextDataFactory}.
53
+ */
54
+ constructor(federation: Federation<TContextData>, ...args: FedifyMiddlewareArgs<TContextData>);
55
+ handle(ctx: HttpContext, next: NextFn): Promise<void>;
56
+ }
57
+ /**
58
+ * Creates a middleware class around a `Federation` the application built
59
+ * itself, with no container or `config/fedify.ts` involved. This is the
60
+ * package's default export; most applications use the provider instead.
61
+ *
62
+ * @example
63
+ * ```ts
64
+ * // start/kernel.ts
65
+ * import fedifyMiddleware from '@fedify/adonisjs'
66
+ * // A module of the application's own that default-exports its `Federation`.
67
+ * import federation from '../app/federation.js'
68
+ *
69
+ * server.use([
70
+ * async () => ({ default: fedifyMiddleware(federation, (ctx) => ctx) }),
71
+ * ])
72
+ * ```
73
+ *
74
+ * With the default `ContextData` of `HttpContext | null`, pass `(ctx) => ctx`
75
+ * to match the provider; see {@link FedifyMiddlewareArgs}.
76
+ *
77
+ * @returns A {@link FedifyMiddleware} subclass whose constructor takes no
78
+ * arguments, which is what `server.use()` constructs.
79
+ * @param federation The federation instance to mount.
80
+ * @param args The context-data factory and the options.
81
+ */
82
+ declare function fedifyMiddleware<TContextData>(federation: Federation<TContextData>, ...args: FedifyMiddlewareArgs<TContextData>): FedifyMiddlewareClass;
83
+ //#endregion
84
+ export { FedifyMiddlewareArgs, FedifyMiddlewareClass, FedifyMiddlewareHandler, FedifyMiddlewareOptions, FedifyMiddleware as default, fedifyMiddleware };
85
+ //# sourceMappingURL=fedify_middleware.d.ts.map
@@ -0,0 +1,505 @@
1
+ import { E_FEDIFY_CONTEXT_UNAVAILABLE, E_MISSING_FEDERATION } from "./errors.js";
2
+ import { ensureFederationGetter, setFederationContextFactory } from "./http_context.js";
3
+ import { PLACEHOLDER_HOST, warnUnusableAuthority } from "./unusable_authority.js";
4
+ import { Readable } from "node:stream";
5
+
6
+ //#region src/fedify_middleware.ts
7
+ /**
8
+ * The AdonisJS server middleware that mounts Fedify.
9
+ *
10
+ * The service provider binds {@link FedifyMiddleware} with the `Federation`
11
+ * and `config/fedify.ts`; applications that build their own `Federation` use
12
+ * {@link fedifyMiddleware} instead. Nothing here depends on the container.
13
+ *
14
+ * @module
15
+ */
16
+ /**
17
+ * Context classes whose conflicting `federation` member was already reported.
18
+ */
19
+ const conflicting = new WeakSet();
20
+ /**
21
+ * Methods `new Request()` refuses; in practice only `TRACE` arrives here.
22
+ */
23
+ const FORBIDDEN_METHODS = new Set([
24
+ "CONNECT",
25
+ "TRACE",
26
+ "TRACK"
27
+ ]);
28
+ /**
29
+ * Wraps the raw `IncomingMessage` in a `ReadableStream` that stays inert until
30
+ * first read. The `Request` is built before Fedify decides whether it handles
31
+ * the request; an eager `Readable.toWeb()` would start draining the socket and
32
+ * hang the AdonisJS body parser on every declined `POST`.
33
+ */
34
+ function lazyRequestBody(message) {
35
+ let reader;
36
+ return new ReadableStream({
37
+ async pull(controller) {
38
+ reader ??= Readable.toWeb(message).getReader();
39
+ const { done, value } = await reader.read();
40
+ if (done) controller.close();
41
+ else controller.enqueue(value);
42
+ },
43
+ cancel(reason) {
44
+ return reader?.cancel(reason);
45
+ }
46
+ }, { highWaterMark: 0 });
47
+ }
48
+ /**
49
+ * What an authority may not contain: `Host` admits only `uri-host [":" port]`,
50
+ * so anything that could carry a path, query, fragment or userinfo makes the
51
+ * value something other than an authority.
52
+ */
53
+ const NON_AUTHORITY = /[/?#\\@\s]/;
54
+ /**
55
+ * Keeps the first entry of a comma-separated authority (`X-Forwarded-Host`
56
+ * behind two proxies), which `new URL()` would reject.
57
+ *
58
+ * Under `trustProxy`, `authority()` returns `X-Forwarded-Host` verbatim, so a
59
+ * client could send `example.com/x` and shift the path Fedify sees to
60
+ * `/x/users/alice` while AdonisJS still routes the real path — bypassing
61
+ * `ignoreRoutePrefixes`. Anything that could carry a path, query, fragment or
62
+ * userinfo (`good.example@evil.com` parses to the host `evil.com`) is
63
+ * therefore refused outright, which leaves the {@link PLACEHOLDER_HOST}
64
+ * fallback to keep the path intact.
65
+ */
66
+ function normalizeAuthority(host) {
67
+ const comma = host.indexOf(",");
68
+ const authority = (comma < 0 ? host : host.slice(0, comma)).trim();
69
+ return NON_AUTHORITY.test(authority) ? "" : authority;
70
+ }
71
+ /**
72
+ * Reduces a request target to origin form, or `undefined` when it carries no
73
+ * path. An absolute target (`GET http://example.com/users/alice HTTP/1.1`)
74
+ * reaches `req.url` verbatim; only its path and query survive, so the request
75
+ * line cannot pick the host Fedify sees and let an inbox `POST` signed for
76
+ * another server verify here.
77
+ */
78
+ function toOriginForm(target) {
79
+ if (target.startsWith("/")) return target;
80
+ const prefix = /^[A-Za-z][A-Za-z0-9+.-]*:\/\/[^/?#]*/.exec(target);
81
+ if (prefix === null) return undefined;
82
+ const rest = target.slice(prefix[0].length);
83
+ return rest.startsWith("/") ? rest : `/${rest}`;
84
+ }
85
+ /**
86
+ * Builds the URL for one authority, or `undefined` if it cannot carry one.
87
+ * The target is interpolated, never resolved, so the authority stays the one
88
+ * {@link normalizeAuthority} passed; an empty one is refused because
89
+ * `new URL("http:///users/alice")` silently takes `users` as the host.
90
+ */
91
+ function buildRequestUrl(protocol, authority, target) {
92
+ if (authority === "") return undefined;
93
+ try {
94
+ return new URL(`${protocol}://${authority}${target}`);
95
+ } catch {
96
+ return undefined;
97
+ }
98
+ }
99
+ /**
100
+ * Builds the URL Fedify sees from the raw request target, keeping the
101
+ * percent-encoding Fedify's URI templates expect and the bytes a peer signed.
102
+ * Only the target's path and query are read (see {@link toOriginForm}); the
103
+ * authority comes from {@link normalizeAuthority}, via `authority()` so
104
+ * HTTP/2's `:authority` is covered. Every input is attacker-controlled, so
105
+ * the result only has to exist: the scheme is reduced to `http`/`https`, and
106
+ * an unusable authority falls back to {@link PLACEHOLDER_HOST} rather than a
107
+ * 500. Since that breaks signature verification, the discarded value is
108
+ * returned for the caller to log once it knows Fedify will see the request.
109
+ *
110
+ * The accepted authority is returned verbatim as well, for the `Host` header
111
+ * {@link toFetchRequest} synthesises on HTTP/2: `URL` normalises what it
112
+ * parses (case, default port, punycode) while a signature covers the bytes as
113
+ * sent. It is absent exactly when the placeholder stood in.
114
+ */
115
+ function toRequestUrl(request) {
116
+ const protocol = request.protocol().toLowerCase() === "https" ? "https" : "http";
117
+ const host = request.authority();
118
+ const authority = host == null ? "" : normalizeAuthority(host);
119
+ const target = toOriginForm(request.request.url ?? "/") ?? "/";
120
+ const parsed = buildRequestUrl(protocol, authority, target);
121
+ let url = parsed;
122
+ let unusableAuthority;
123
+ if (url === undefined) {
124
+ url = new URL(`${protocol}://${PLACEHOLDER_HOST}${target}`);
125
+ if (host != null) unusableAuthority = host;
126
+ }
127
+ return {
128
+ url,
129
+ authority: parsed === undefined ? undefined : authority,
130
+ unusableAuthority
131
+ };
132
+ }
133
+ /**
134
+ * Converts an AdonisJS request into a WHATWG `Request` for Fedify.
135
+ *
136
+ * `intended()`, not `method()`, so `_method` spoofing cannot turn an inbox
137
+ * `POST` into a `GET`. Headers are forwarded as they arrived; HTTP/2
138
+ * pseudo-headers are skipped, and since that leaves no `Host` (which
139
+ * signatures may cover), one is synthesised only then, from the authority the
140
+ * URL was built from.
141
+ */
142
+ function toFetchRequest(ctx, url, authority) {
143
+ const request = ctx.request;
144
+ const method = request.intended().toUpperCase();
145
+ const headers = new Headers();
146
+ const rawHeaders = request.headers();
147
+ for (const key of Object.keys(rawHeaders)) {
148
+ if (key.startsWith(":")) continue;
149
+ const value = rawHeaders[key];
150
+ if (Array.isArray(value)) {
151
+ for (const item of value) headers.append(key, item);
152
+ } else if (typeof value === "string") {
153
+ headers.append(key, value);
154
+ }
155
+ }
156
+ if (authority !== undefined && !headers.has("host")) {
157
+ headers.set("host", authority);
158
+ }
159
+ const hasBody = method !== "GET" && method !== "HEAD";
160
+ return new Request(url, {
161
+ method,
162
+ headers,
163
+ body: hasBody ? lazyRequestBody(request.request) : undefined,
164
+ ...{ duplex: "half" }
165
+ });
166
+ }
167
+ /**
168
+ * Copies a WHATWG `Response` produced by Fedify onto the AdonisJS response.
169
+ * `Set-Cookie` goes through `getSetCookie()` because iterating `Headers`
170
+ * folds it; `Vary` is merged rather than copied, so a `Vary: Origin` an
171
+ * upstream middleware set survives Fedify's own `Vary: Accept`; bodyless
172
+ * responses (`204`, `304`, `HEAD`) are not streamed.
173
+ */
174
+ function writeFedifyResponse(response, ctx) {
175
+ ctx.response.status(response.status);
176
+ for (const [key, value] of response.headers) {
177
+ const name = key.toLowerCase();
178
+ if (name === "set-cookie") continue;
179
+ if (name === "vary") {
180
+ mergeIntoVary(ctx, value);
181
+ continue;
182
+ }
183
+ ctx.response.header(key, value);
184
+ }
185
+ for (const cookie of response.headers.getSetCookie()) {
186
+ ctx.response.append("set-cookie", cookie);
187
+ }
188
+ const bodyless = response.body === null || response.status === 204 || response.status === 304 || ctx.request.intended().toUpperCase() === "HEAD";
189
+ if (bodyless) {
190
+ response.body?.cancel().catch(() => {});
191
+ return;
192
+ }
193
+ ctx.response.stream(response.body, (error) => {
194
+ ctx.logger.error({ err: error }, "Failed to stream the Fedify response");
195
+ return ["Internal server error", 500];
196
+ });
197
+ }
198
+ /**
199
+ * Splits a `Vary` value into its entries.
200
+ */
201
+ function parseVary(value) {
202
+ return value.split(",").map((entry) => entry.trim()).filter((entry) => entry !== "");
203
+ }
204
+ /**
205
+ * Merges a raw `Vary` value into the response's `Vary`.
206
+ *
207
+ * `ctx.response.header()` replaces rather than appends, so writing `Vary`
208
+ * directly would drop what an upstream middleware or a route already put
209
+ * there (CORS's `Origin`, say) and let a shared cache mix representations.
210
+ *
211
+ * Both of AdonisJS's header layers are read: `header()` buffers a value that
212
+ * is copied onto the Node response when the reply is written, while
213
+ * `ctx.response.vary()` writes to the Node response directly, and
214
+ * `getHeader()` prefers the buffered value. Once `Accept` is buffered before
215
+ * `next()`, a `vary("Origin")` a route calls is therefore invisible through
216
+ * `getHeader()` alone and would be overwritten at write time. The union goes
217
+ * back to the buffered layer, since that is what goes out. `Vary: *` on any
218
+ * side wins.
219
+ */
220
+ function mergeIntoVary(ctx, rawValue) {
221
+ const vary = [];
222
+ const layers = [
223
+ String(ctx.response.getHeader("Vary") ?? ""),
224
+ String(ctx.response.response.getHeader("vary") ?? ""),
225
+ rawValue
226
+ ];
227
+ for (const layer of layers) {
228
+ for (const value of parseVary(layer)) {
229
+ if (value === "*") {
230
+ ctx.response.header("Vary", "*");
231
+ return;
232
+ }
233
+ if (!vary.some((v) => v.toLowerCase() === value.toLowerCase())) {
234
+ vary.push(value);
235
+ }
236
+ }
237
+ }
238
+ if (vary.length > 0) ctx.response.header("Vary", vary.join(", "));
239
+ }
240
+ /**
241
+ * Headers of a discarded response that must not describe the one written over
242
+ * it: cache and validator metadata, which decides how long a shared cache
243
+ * keeps the substitute, and metadata about a body nobody will see. The
244
+ * targeted cache-control family (`CDN-Cache-Control`, `Surrogate-Control`, …)
245
+ * is included because a CDN honours it over `Cache-Control` and largely
246
+ * ignores `Vary`, so a leftover lifetime would keep the 406 cached for
247
+ * ActivityPub clients too. Enumerated rather than matched on a `content-`
248
+ * prefix, which would also take `Content-Security-Policy`.
249
+ */
250
+ const DISCARDED_ENTITY_HEADERS = new Set([
251
+ "age",
252
+ "cache-control",
253
+ "cdn-cache-control",
254
+ "cloudflare-cdn-cache-control",
255
+ "content-digest",
256
+ "content-disposition",
257
+ "content-encoding",
258
+ "content-language",
259
+ "content-length",
260
+ "content-location",
261
+ "content-range",
262
+ "content-type",
263
+ "digest",
264
+ "etag",
265
+ "expires",
266
+ "last-modified",
267
+ "pragma",
268
+ "repr-digest",
269
+ "surrogate-control",
270
+ "surrogate-key"
271
+ ]);
272
+ /**
273
+ * Removes a response header around a quirk of AdonisJS's `removeHeader()`,
274
+ * which drops the buffered entry only when the current value is truthy, so one
275
+ * set to `""` would survive. It also forwards to Node unconditionally, which
276
+ * throws once the head is flushed, so this is only safe while
277
+ * `ctx.response.headersSent` is false.
278
+ */
279
+ function removeResponseHeader(ctx, key) {
280
+ ctx.response.header(key, "-");
281
+ ctx.response.removeHeader(key);
282
+ }
283
+ /**
284
+ * Drops the body a discarded response left buffered, so the one written over
285
+ * it is the one that goes out.
286
+ *
287
+ * `response.send()` assigns `lazyBody.content` alone, and `finish()` reads the
288
+ * other entries after it: a path passed to `download()` would still be
289
+ * streamed in place of the new body, and a readable passed to `stream()`
290
+ * would be forgotten — one leaked file descriptor per request. Resetting
291
+ * `lazyBody` drops both.
292
+ */
293
+ function discardPendingBody(ctx) {
294
+ const stream = ctx.response.outgoingStream;
295
+ ctx.response.lazyBody = {};
296
+ if (stream == null) return;
297
+ if ("destroy" in stream) {
298
+ stream.on("error", () => {});
299
+ if (!stream.destroyed) stream.destroy();
300
+ } else if (!stream.locked) {
301
+ stream.cancel().catch(() => {});
302
+ }
303
+ }
304
+ /**
305
+ * Undoes what a discarded response did to the headers, before another is
306
+ * written over it.
307
+ *
308
+ * `next()` mixes two populations: a header a *later middleware* set
309
+ * (`@adonisjs/cors`'s `Access-Control-Allow-Origin`, say) answers this
310
+ * request and has to survive, while one the *discarded page* set describes a
311
+ * body nobody will see. They are told apart by name: only
312
+ * {@link DISCARDED_ENTITY_HEADERS} are touched, and each is put back exactly
313
+ * as the snapshot had it — removed when the snapshot had none. Restoring
314
+ * every snapshot entry instead would undo the request's own answer, since
315
+ * `response.header()` replaces rather than merges.
316
+ *
317
+ * `Vary` is reconciled by {@link mergeIntoVary} around `next()` instead,
318
+ * since only the union of the snapshot and the current value is right.
319
+ */
320
+ function rollBackDiscardedResponse(ctx, headersBefore) {
321
+ const headersAfter = ctx.response.getHeaders();
322
+ for (const key of DISCARDED_ENTITY_HEADERS) {
323
+ const before = Object.hasOwn(headersBefore, key) ? headersBefore[key] : undefined;
324
+ if (before !== undefined) ctx.response.header(key, before);
325
+ else if (Object.hasOwn(headersAfter, key)) removeResponseHeader(ctx, key);
326
+ }
327
+ }
328
+ /**
329
+ * The response handed back to Fedify after AdonisJS answered a delegated
330
+ * request. Nothing is written from it, but Fedify logs and meters its
331
+ * status, so it reports what AdonisJS answered. `Response` only accepts
332
+ * 200-599, so anything else (a 1xx) is reported as 200.
333
+ */
334
+ function delegatedResponse(status) {
335
+ return new Response(null, { status: status >= 200 && status <= 599 ? status : 200 });
336
+ }
337
+ /**
338
+ * Mounts a `Federation` instance inside an AdonisJS application; the
339
+ * counterpart of `integrateFederation()` in `@fedify/express`.
340
+ *
341
+ * Register it in the **server** stack: Fedify serves paths the router does
342
+ * not know, and the body stream is still intact there for signature
343
+ * verification. An instance holds configuration only, so one serves every
344
+ * request.
345
+ */
346
+ var FedifyMiddleware = class {
347
+ #federation;
348
+ #contextDataFactory;
349
+ #ignoreRoutePrefixes;
350
+ /**
351
+ * @param federation The federation instance to mount.
352
+ * @param args The context-data factory and the options; see
353
+ * {@link FedifyMiddlewareArgs}. The factory sees a pre-routing
354
+ * `HttpContext`; see {@link import('./types.js').ContextDataFactory}.
355
+ */
356
+ constructor(federation, ...args) {
357
+ if (federation == null) throw new E_MISSING_FEDERATION();
358
+ const [contextDataFactory, options] = args;
359
+ this.#federation = federation;
360
+ this.#contextDataFactory = contextDataFactory ?? (() => undefined);
361
+ this.#ignoreRoutePrefixes = options?.ignoreRoutePrefixes ?? [];
362
+ }
363
+ async handle(ctx, next) {
364
+ const federation = this.#federation;
365
+ const contextClass = ctx.constructor;
366
+ if (!ensureFederationGetter(contextClass) && !conflicting.has(contextClass)) {
367
+ conflicting.add(contextClass);
368
+ ctx.logger.error("Something other than @fedify/adonisjs defines \"federation\" on this " + "application's HttpContext, so \"ctx.federation\" is not the Fedify " + "request context. Federation endpoints are unaffected; rename the " + "conflicting macro or getter to use it from controllers");
369
+ }
370
+ const { url, authority, unusableAuthority } = toRequestUrl(ctx.request);
371
+ const path = url.pathname;
372
+ if (this.#ignoreRoutePrefixes.some((prefix) => path.startsWith(prefix)) || FORBIDDEN_METHODS.has(ctx.request.intended().toUpperCase())) {
373
+ setFederationContextFactory(ctx, () => {
374
+ throw new E_FEDIFY_CONTEXT_UNAVAILABLE(path);
375
+ });
376
+ return next();
377
+ }
378
+ if (unusableAuthority !== undefined) {
379
+ warnUnusableAuthority(ctx.logger, unusableAuthority);
380
+ }
381
+ const request = toFetchRequest(ctx, url, authority);
382
+ const contextData = await this.#contextDataFactory(ctx);
383
+ setFederationContextFactory(ctx, () => federation.createContext(request, contextData));
384
+ /**
385
+ * What happened while Fedify was deciding: whether a fall-through callback
386
+ * handed the request to AdonisJS, and whether AdonisJS produced nothing of
387
+ * its own so the 406 below is the answer. "Nothing" means no route
388
+ * matched (`ctx.route` is set only on a match) *and* the framework's own
389
+ * 404: a later server middleware (`@adonisjs/cors` on a preflight) or a
390
+ * downstream failure can answer without the router running, and must not
391
+ * be overwritten. A downstream middleware *deliberately* answering 404 on
392
+ * a Fedify-owned path is indistinguishable from that default and is also
393
+ * replaced by the 406.
394
+ *
395
+ * `router.match()` could say before `next()` whether a route exists and
396
+ * spare the rollback, but is deliberately not asked:
397
+ *
398
+ * 1. Every integration follows the deferred-406 contract the manual
399
+ * documents: delegate, and answer 406 only when the framework answered
400
+ * 404 (Hono reads `ctx.res.status` after `next()`, Express `req.route`).
401
+ * Deciding from the route table would give AdonisJS a narrower one.
402
+ *
403
+ * 2. Server middleware answers without a route by design (`@adonisjs/cors`
404
+ * on a preflight, `@adonisjs/static`, a maintenance page), and Fedify
405
+ * calls `onNotAcceptable` for a wildcard `Accept` too -- what curl and
406
+ * a preflight send -- so skipping `next()` would cut those off on every
407
+ * path Fedify owns.
408
+ *
409
+ * 3. `ctx.route` is the framework's own verdict. A lookup here would
410
+ * duplicate `routeFinder`'s preamble and drift silently as the router
411
+ * changes.
412
+ *
413
+ * The price is undoing a response AdonisJS was allowed to start: the
414
+ * headers in {@link DISCARDED_ENTITY_HEADERS} are put back by name, a
415
+ * pending body is dropped, and nothing is touched once the head has been
416
+ * flushed.
417
+ *
418
+ * An object rather than two `let`s so TypeScript does not narrow them.
419
+ */
420
+ const outcome = {
421
+ delegated: false,
422
+ answer406: false
423
+ };
424
+ const response = await federation.fetch(request, {
425
+ contextData,
426
+ /**
427
+ * Fedify has no route for this path: hand the request to AdonisJS.
428
+ * `await next()` resolves even on no match, since `E_ROUTE_NOT_FOUND`
429
+ * goes through the exception handler and renders a 404.
430
+ */
431
+ onNotFound: async () => {
432
+ outcome.delegated = true;
433
+ await next();
434
+ return delegatedResponse(ctx.response.getStatus());
435
+ },
436
+ /**
437
+ * Fedify has a route but cannot satisfy `Accept` (a browser at an actor
438
+ * URL). AdonisJS gets a chance to serve its own representation first;
439
+ * 406 is only the answer if it has nothing either. `Accept` is merged
440
+ * into `Vary` rather than written over it, and the snapshot's value is
441
+ * merged back after `next()`, because a route may have replaced the
442
+ * header meanwhile.
443
+ */
444
+ onNotAcceptable: async () => {
445
+ outcome.delegated = true;
446
+ mergeIntoVary(ctx, "Accept");
447
+ const headersBefore = ctx.response.getHeaders();
448
+ await next();
449
+ mergeIntoVary(ctx, String(headersBefore.vary ?? ""));
450
+ const status = ctx.response.getStatus();
451
+ outcome.answer406 = ctx.route === undefined && status === 404 && !ctx.response.headersSent;
452
+ if (outcome.answer406) rollBackDiscardedResponse(ctx, headersBefore);
453
+ return delegatedResponse(outcome.answer406 ? 406 : status);
454
+ }
455
+ });
456
+ if (outcome.answer406) {
457
+ discardPendingBody(ctx);
458
+ ctx.response.status(406).type("text/plain").send("Not acceptable");
459
+ return;
460
+ }
461
+ if (outcome.delegated) {
462
+ return;
463
+ }
464
+ writeFedifyResponse(response, ctx);
465
+ }
466
+ };
467
+ /**
468
+ * Creates a middleware class around a `Federation` the application built
469
+ * itself, with no container or `config/fedify.ts` involved. This is the
470
+ * package's default export; most applications use the provider instead.
471
+ *
472
+ * @example
473
+ * ```ts
474
+ * // start/kernel.ts
475
+ * import fedifyMiddleware from '@fedify/adonisjs'
476
+ * // A module of the application's own that default-exports its `Federation`.
477
+ * import federation from '../app/federation.js'
478
+ *
479
+ * server.use([
480
+ * async () => ({ default: fedifyMiddleware(federation, (ctx) => ctx) }),
481
+ * ])
482
+ * ```
483
+ *
484
+ * With the default `ContextData` of `HttpContext | null`, pass `(ctx) => ctx`
485
+ * to match the provider; see {@link FedifyMiddlewareArgs}.
486
+ *
487
+ * @returns A {@link FedifyMiddleware} subclass whose constructor takes no
488
+ * arguments, which is what `server.use()` constructs.
489
+ * @param federation The federation instance to mount.
490
+ * @param args The context-data factory and the options.
491
+ */
492
+ function fedifyMiddleware(federation, ...args) {
493
+ if (federation == null) {
494
+ throw new E_MISSING_FEDERATION("fedifyMiddleware() was called without a Federation instance. Check " + "that the module its first argument comes from actually " + "default-exports the Federation it builds");
495
+ }
496
+ return class ConfiguredFedifyMiddleware extends FedifyMiddleware {
497
+ constructor() {
498
+ super(federation, ...args);
499
+ }
500
+ };
501
+ }
502
+
503
+ //#endregion
504
+ export { FedifyMiddleware as default, fedifyMiddleware };
505
+ //# sourceMappingURL=fedify_middleware.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fedify_middleware.js","names":["#federation","#contextDataFactory","#ignoreRoutePrefixes"],"sources":["../../src/fedify_middleware.ts"],"sourcesContent":["/**\n * The AdonisJS server middleware that mounts Fedify.\n *\n * The service provider binds {@link FedifyMiddleware} with the `Federation`\n * and `config/fedify.ts`; applications that build their own `Federation` use\n * {@link fedifyMiddleware} instead. Nothing here depends on the container.\n *\n * @module\n */\nimport { Readable } from \"node:stream\";\n\nimport type { HttpContext } from \"@adonisjs/core/http\";\nimport type { NextFn } from \"@adonisjs/core/types/http\";\nimport type { Federation } from \"@fedify/fedify\";\n\nimport {\n E_FEDIFY_CONTEXT_UNAVAILABLE,\n E_MISSING_FEDERATION,\n} from \"./errors.ts\";\nimport {\n ensureFederationGetter,\n type HttpContextClass,\n setFederationContextFactory,\n} from \"./http_context.ts\";\nimport type { ContextData, ContextDataFactory } from \"./types.ts\";\nimport {\n PLACEHOLDER_HOST,\n warnUnusableAuthority,\n} from \"./unusable_authority.ts\";\n\n/**\n * Options accepted by {@link FedifyMiddleware} and {@link fedifyMiddleware}.\n */\nexport interface FedifyMiddlewareOptions {\n /**\n * URL path prefixes to bypass entirely. See\n * {@link import('./types.js').FedifyConfig.ignoreRoutePrefixes}.\n *\n * @default `[]`\n */\n ignoreRoutePrefixes?: string[];\n}\n\n/**\n * The instance shape of the middleware.\n */\nexport interface FedifyMiddlewareHandler {\n handle(ctx: HttpContext, next: NextFn): Promise<void>;\n}\n\n/**\n * What {@link fedifyMiddleware} returns: a middleware **class**, since\n * `server.use()` constructs entries through the container.\n */\nexport type FedifyMiddlewareClass = new () => FedifyMiddlewareHandler;\n\n/**\n * The arguments after `federation`. The factory defaults to producing\n * `undefined`, so it may only be omitted when `TContextData` admits it;\n * otherwise omitting it is a compile error. The tuple wrapper keeps the\n * conditional from distributing over a union.\n */\nexport type FedifyMiddlewareArgs<TContextData> = [undefined] extends\n [TContextData] ? [\n contextDataFactory?: ContextDataFactory<TContextData>,\n options?: FedifyMiddlewareOptions,\n ]\n : [\n contextDataFactory: ContextDataFactory<TContextData>,\n options?: FedifyMiddlewareOptions,\n ];\n\n/**\n * Context classes whose conflicting `federation` member was already reported.\n */\nconst conflicting = new WeakSet<object>();\n\n/**\n * Methods `new Request()` refuses; in practice only `TRACE` arrives here.\n */\nconst FORBIDDEN_METHODS = new Set([\"CONNECT\", \"TRACE\", \"TRACK\"]);\n\n/**\n * Wraps the raw `IncomingMessage` in a `ReadableStream` that stays inert until\n * first read. The `Request` is built before Fedify decides whether it handles\n * the request; an eager `Readable.toWeb()` would start draining the socket and\n * hang the AdonisJS body parser on every declined `POST`.\n */\nfunction lazyRequestBody(\n message: HttpContext[\"request\"][\"request\"],\n): ReadableStream<Uint8Array> {\n let reader: ReadableStreamDefaultReader<Uint8Array> | undefined;\n\n return new ReadableStream<Uint8Array>(\n {\n async pull(controller) {\n reader ??= (Readable.toWeb(message) as ReadableStream<Uint8Array>)\n .getReader();\n\n const { done, value } = await reader.read();\n if (done) controller.close();\n else controller.enqueue(value);\n },\n cancel(reason) {\n return reader?.cancel(reason);\n },\n },\n { highWaterMark: 0 },\n );\n}\n\n/**\n * What an authority may not contain: `Host` admits only `uri-host [\":\" port]`,\n * so anything that could carry a path, query, fragment or userinfo makes the\n * value something other than an authority.\n */\nconst NON_AUTHORITY = /[/?#\\\\@\\s]/;\n\n/**\n * Keeps the first entry of a comma-separated authority (`X-Forwarded-Host`\n * behind two proxies), which `new URL()` would reject.\n *\n * Under `trustProxy`, `authority()` returns `X-Forwarded-Host` verbatim, so a\n * client could send `example.com/x` and shift the path Fedify sees to\n * `/x/users/alice` while AdonisJS still routes the real path — bypassing\n * `ignoreRoutePrefixes`. Anything that could carry a path, query, fragment or\n * userinfo (`good.example@evil.com` parses to the host `evil.com`) is\n * therefore refused outright, which leaves the {@link PLACEHOLDER_HOST}\n * fallback to keep the path intact.\n */\nfunction normalizeAuthority(host: string): string {\n const comma = host.indexOf(\",\");\n const authority = (comma < 0 ? host : host.slice(0, comma)).trim();\n return NON_AUTHORITY.test(authority) ? \"\" : authority;\n}\n\n/**\n * Reduces a request target to origin form, or `undefined` when it carries no\n * path. An absolute target (`GET http://example.com/users/alice HTTP/1.1`)\n * reaches `req.url` verbatim; only its path and query survive, so the request\n * line cannot pick the host Fedify sees and let an inbox `POST` signed for\n * another server verify here.\n */\nfunction toOriginForm(target: string): string | undefined {\n // Origin form already. It is interpolated, not resolved, so `//users/alice`\n // keeps its path instead of reading `users` as a host.\n if (target.startsWith(\"/\")) return target;\n\n // Textually rather than through `new URL()`, which would hand back the\n // *normalised* path or, for a target it refuses, nothing at all --\n // collapsing the request to `/` and hiding the real path from\n // `ignoreRoutePrefixes` and `ctx.federation`.\n const prefix = /^[A-Za-z][A-Za-z0-9+.-]*:\\/\\/[^/?#]*/.exec(target);\n // Asterisk form (`OPTIONS *`) or malformed. Authority form reaches only\n // `CONNECT`, which {@link FORBIDDEN_METHODS} refuses.\n if (prefix === null) return undefined;\n\n const rest = target.slice(prefix[0].length);\n return rest.startsWith(\"/\") ? rest : `/${rest}`;\n}\n\n/**\n * Builds the URL for one authority, or `undefined` if it cannot carry one.\n * The target is interpolated, never resolved, so the authority stays the one\n * {@link normalizeAuthority} passed; an empty one is refused because\n * `new URL(\"http:///users/alice\")` silently takes `users` as the host.\n */\nfunction buildRequestUrl(\n protocol: string,\n authority: string,\n target: string,\n): URL | undefined {\n if (authority === \"\") return undefined;\n\n try {\n return new URL(`${protocol}://${authority}${target}`);\n } catch {\n return undefined;\n }\n}\n\n/**\n * Builds the URL Fedify sees from the raw request target, keeping the\n * percent-encoding Fedify's URI templates expect and the bytes a peer signed.\n * Only the target's path and query are read (see {@link toOriginForm}); the\n * authority comes from {@link normalizeAuthority}, via `authority()` so\n * HTTP/2's `:authority` is covered. Every input is attacker-controlled, so\n * the result only has to exist: the scheme is reduced to `http`/`https`, and\n * an unusable authority falls back to {@link PLACEHOLDER_HOST} rather than a\n * 500. Since that breaks signature verification, the discarded value is\n * returned for the caller to log once it knows Fedify will see the request.\n *\n * The accepted authority is returned verbatim as well, for the `Host` header\n * {@link toFetchRequest} synthesises on HTTP/2: `URL` normalises what it\n * parses (case, default port, punycode) while a signature covers the bytes as\n * sent. It is absent exactly when the placeholder stood in.\n */\nfunction toRequestUrl(\n request: HttpContext[\"request\"],\n): { url: URL; authority?: string; unusableAuthority?: string } {\n // `protocol()` hands back the first `X-Forwarded-Proto` token as it\n // arrived; an upper-case `HTTPS` must not be read as plain `http`.\n const protocol = request.protocol().toLowerCase() === \"https\"\n ? \"https\"\n : \"http\";\n const host = request.authority();\n const authority = host == null ? \"\" : normalizeAuthority(host);\n // A target with no origin form is routed as `/`, which matches no\n // federation route, so AdonisJS answers.\n const target = toOriginForm(request.request.url ?? \"/\") ?? \"/\";\n\n const parsed = buildRequestUrl(protocol, authority, target);\n let url = parsed;\n let unusableAuthority: string | undefined;\n if (url === undefined) {\n // Only the authority can be at fault: the target starts with `/`, and a\n // URL parser in path state takes any bytes after it.\n url = new URL(`${protocol}://${PLACEHOLDER_HOST}${target}`);\n if (host != null) unusableAuthority = host;\n }\n\n return {\n url,\n authority: parsed === undefined ? undefined : authority,\n unusableAuthority,\n };\n}\n\n/**\n * Converts an AdonisJS request into a WHATWG `Request` for Fedify.\n *\n * `intended()`, not `method()`, so `_method` spoofing cannot turn an inbox\n * `POST` into a `GET`. Headers are forwarded as they arrived; HTTP/2\n * pseudo-headers are skipped, and since that leaves no `Host` (which\n * signatures may cover), one is synthesised only then, from the authority the\n * URL was built from.\n */\nfunction toFetchRequest(\n ctx: HttpContext,\n url: URL,\n authority: string | undefined,\n): Request {\n const request = ctx.request;\n const method = request.intended().toUpperCase();\n\n const headers = new Headers();\n const rawHeaders = request.headers();\n // `Object.keys()`, not `for...in`: a polluted prototype must not be forwarded.\n for (const key of Object.keys(rawHeaders)) {\n if (key.startsWith(\":\")) continue;\n const value = rawHeaders[key];\n if (Array.isArray(value)) {\n for (const item of value) headers.append(key, item);\n } else if (typeof value === \"string\") {\n headers.append(key, value);\n }\n }\n\n // Only when absent: an HTTP/1.1 `Host` is what the peer signed and what the\n // verifier reads, so it is never touched (a proxy rewriting `Host` has to\n // preserve it; see the README). On HTTP/2 the authority travels in\n // `:authority`, which the pseudo-header skip dropped, so it is put back\n // here -- verbatim, not `url.host`, since `URL` case-folds, drops a default\n // port and punycodes while a signature covers the bytes as sent. An\n // unusable authority gets the placeholder in the URL only, never a header.\n if (authority !== undefined && !headers.has(\"host\")) {\n headers.set(\"host\", authority);\n }\n\n const hasBody = method !== \"GET\" && method !== \"HEAD\";\n\n return new Request(url, {\n method,\n headers,\n body: hasBody ? lazyRequestBody(request.request) : undefined,\n // Not in the DOM lib's `RequestInit`, but Node requires it for streams.\n ...{ duplex: \"half\" },\n });\n}\n\n/**\n * Copies a WHATWG `Response` produced by Fedify onto the AdonisJS response.\n * `Set-Cookie` goes through `getSetCookie()` because iterating `Headers`\n * folds it; `Vary` is merged rather than copied, so a `Vary: Origin` an\n * upstream middleware set survives Fedify's own `Vary: Accept`; bodyless\n * responses (`204`, `304`, `HEAD`) are not streamed.\n */\nfunction writeFedifyResponse(response: Response, ctx: HttpContext): void {\n ctx.response.status(response.status);\n\n for (const [key, value] of response.headers) {\n const name = key.toLowerCase();\n if (name === \"set-cookie\") continue;\n if (name === \"vary\") {\n mergeIntoVary(ctx, value);\n continue;\n }\n ctx.response.header(key, value);\n }\n\n for (const cookie of response.headers.getSetCookie()) {\n ctx.response.append(\"set-cookie\", cookie);\n }\n\n const bodyless = response.body === null ||\n response.status === 204 ||\n response.status === 304 ||\n // `intended()`: a spoofed `_method=HEAD` must not suppress the body.\n ctx.request.intended().toUpperCase() === \"HEAD\";\n\n if (bodyless) {\n // Do not leave a body Fedify produced anyway (usually for HEAD) dangling;\n // a failed cleanup must not become an unhandled rejection.\n response.body?.cancel().catch(() => {});\n return;\n }\n\n ctx.response.stream(response.body!, (error) => {\n ctx.logger.error({ err: error }, \"Failed to stream the Fedify response\");\n return [\"Internal server error\", 500];\n });\n}\n\n/**\n * Splits a `Vary` value into its entries.\n */\nfunction parseVary(value: string): string[] {\n return value\n .split(\",\")\n .map((entry) => entry.trim())\n .filter((entry) => entry !== \"\");\n}\n\n/**\n * Merges a raw `Vary` value into the response's `Vary`.\n *\n * `ctx.response.header()` replaces rather than appends, so writing `Vary`\n * directly would drop what an upstream middleware or a route already put\n * there (CORS's `Origin`, say) and let a shared cache mix representations.\n *\n * Both of AdonisJS's header layers are read: `header()` buffers a value that\n * is copied onto the Node response when the reply is written, while\n * `ctx.response.vary()` writes to the Node response directly, and\n * `getHeader()` prefers the buffered value. Once `Accept` is buffered before\n * `next()`, a `vary(\"Origin\")` a route calls is therefore invisible through\n * `getHeader()` alone and would be overwritten at write time. The union goes\n * back to the buffered layer, since that is what goes out. `Vary: *` on any\n * side wins.\n */\nfunction mergeIntoVary(ctx: HttpContext, rawValue: string): void {\n const vary: string[] = [];\n const layers = [\n String(ctx.response.getHeader(\"Vary\") ?? \"\"),\n String(ctx.response.response.getHeader(\"vary\") ?? \"\"),\n rawValue,\n ];\n\n for (const layer of layers) {\n for (const value of parseVary(layer)) {\n if (value === \"*\") {\n ctx.response.header(\"Vary\", \"*\");\n return;\n }\n if (!vary.some((v) => v.toLowerCase() === value.toLowerCase())) {\n vary.push(value);\n }\n }\n }\n\n if (vary.length > 0) ctx.response.header(\"Vary\", vary.join(\", \"));\n}\n\n/**\n * Headers of a discarded response that must not describe the one written over\n * it: cache and validator metadata, which decides how long a shared cache\n * keeps the substitute, and metadata about a body nobody will see. The\n * targeted cache-control family (`CDN-Cache-Control`, `Surrogate-Control`, …)\n * is included because a CDN honours it over `Cache-Control` and largely\n * ignores `Vary`, so a leftover lifetime would keep the 406 cached for\n * ActivityPub clients too. Enumerated rather than matched on a `content-`\n * prefix, which would also take `Content-Security-Policy`.\n */\nconst DISCARDED_ENTITY_HEADERS: ReadonlySet<string> = new Set([\n \"age\",\n \"cache-control\",\n \"cdn-cache-control\",\n \"cloudflare-cdn-cache-control\",\n \"content-digest\",\n \"content-disposition\",\n \"content-encoding\",\n \"content-language\",\n \"content-length\",\n \"content-location\",\n \"content-range\",\n \"content-type\",\n \"digest\",\n \"etag\",\n \"expires\",\n \"last-modified\",\n \"pragma\",\n \"repr-digest\",\n \"surrogate-control\",\n \"surrogate-key\",\n]);\n\n/**\n * Removes a response header around a quirk of AdonisJS's `removeHeader()`,\n * which drops the buffered entry only when the current value is truthy, so one\n * set to `\"\"` would survive. It also forwards to Node unconditionally, which\n * throws once the head is flushed, so this is only safe while\n * `ctx.response.headersSent` is false.\n */\nfunction removeResponseHeader(ctx: HttpContext, key: string): void {\n // A non-empty placeholder, so the buffered entry is truthy and deleted.\n ctx.response.header(key, \"-\");\n ctx.response.removeHeader(key);\n}\n\n/**\n * Drops the body a discarded response left buffered, so the one written over\n * it is the one that goes out.\n *\n * `response.send()` assigns `lazyBody.content` alone, and `finish()` reads the\n * other entries after it: a path passed to `download()` would still be\n * streamed in place of the new body, and a readable passed to `stream()`\n * would be forgotten — one leaked file descriptor per request. Resetting\n * `lazyBody` drops both.\n */\nfunction discardPendingBody(ctx: HttpContext): void {\n const stream = ctx.response.outgoingStream;\n ctx.response.lazyBody = {};\n if (stream == null) return;\n\n // `ResponseStream` is either a Node `Readable` or a WHATWG `ReadableStream`.\n if (\"destroy\" in stream) {\n // Nothing else listens, since AdonisJS never attached to the stream, and\n // an unlistened `error` event is an uncaught exception.\n stream.on(\"error\", () => {});\n if (!stream.destroyed) stream.destroy();\n } else if (!stream.locked) {\n // Likewise, a rejected `cancel()` must not become an unhandled rejection.\n stream.cancel().catch(() => {});\n }\n}\n\n/**\n * Undoes what a discarded response did to the headers, before another is\n * written over it.\n *\n * `next()` mixes two populations: a header a *later middleware* set\n * (`@adonisjs/cors`'s `Access-Control-Allow-Origin`, say) answers this\n * request and has to survive, while one the *discarded page* set describes a\n * body nobody will see. They are told apart by name: only\n * {@link DISCARDED_ENTITY_HEADERS} are touched, and each is put back exactly\n * as the snapshot had it — removed when the snapshot had none. Restoring\n * every snapshot entry instead would undo the request's own answer, since\n * `response.header()` replaces rather than merges.\n *\n * `Vary` is reconciled by {@link mergeIntoVary} around `next()` instead,\n * since only the union of the snapshot and the current value is right.\n */\nfunction rollBackDiscardedResponse(\n ctx: HttpContext,\n headersBefore: ReturnType<HttpContext[\"response\"][\"getHeaders\"]>,\n): void {\n const headersAfter = ctx.response.getHeaders();\n\n for (const key of DISCARDED_ENTITY_HEADERS) {\n // `hasOwn()`, not `in`: `constructor` is a legal header name.\n const before = Object.hasOwn(headersBefore, key)\n ? headersBefore[key]\n : undefined;\n\n // Written back whether or not `next()` changed it: `header()` is\n // idempotent, and takes an array, so a repeated header goes back whole.\n if (before !== undefined) ctx.response.header(key, before);\n else if (Object.hasOwn(headersAfter, key)) removeResponseHeader(ctx, key);\n }\n}\n\n/**\n * The response handed back to Fedify after AdonisJS answered a delegated\n * request. Nothing is written from it, but Fedify logs and meters its\n * status, so it reports what AdonisJS answered. `Response` only accepts\n * 200-599, so anything else (a 1xx) is reported as 200.\n */\nfunction delegatedResponse(status: number): Response {\n return new Response(null, {\n status: status >= 200 && status <= 599 ? status : 200,\n });\n}\n\n/**\n * Mounts a `Federation` instance inside an AdonisJS application; the\n * counterpart of `integrateFederation()` in `@fedify/express`.\n *\n * Register it in the **server** stack: Fedify serves paths the router does\n * not know, and the body stream is still intact there for signature\n * verification. An instance holds configuration only, so one serves every\n * request.\n */\nexport default class FedifyMiddleware<TContextData = ContextData>\n implements FedifyMiddlewareHandler {\n #federation: Federation<TContextData>;\n #contextDataFactory: ContextDataFactory<TContextData>;\n #ignoreRoutePrefixes: string[];\n\n /**\n * @param federation The federation instance to mount.\n * @param args The context-data factory and the options; see\n * {@link FedifyMiddlewareArgs}. The factory sees a pre-routing\n * `HttpContext`; see {@link import('./types.js').ContextDataFactory}.\n */\n constructor(\n federation: Federation<TContextData>,\n ...args: FedifyMiddlewareArgs<TContextData>\n ) {\n // The container zero-arg-constructs this class when the object the kernel\n // resolved is not the one the provider bound (a duplicated package copy,\n // or the ESM and CJS builds mixed); fail here rather than on every request.\n if (federation == null) throw new E_MISSING_FEDERATION();\n\n // The conditional tuple is unresolved here; read it through its common shape.\n const [contextDataFactory, options] = args as [\n ContextDataFactory<TContextData>?,\n FedifyMiddlewareOptions?,\n ];\n\n this.#federation = federation;\n this.#contextDataFactory = contextDataFactory ??\n (() => undefined as TContextData);\n this.#ignoreRoutePrefixes = options?.ignoreRoutePrefixes ?? [];\n }\n\n async handle(ctx: HttpContext, next: NextFn): Promise<void> {\n const federation = this.#federation;\n\n // Install the `ctx.federation` getter on this context's class: it covers a\n // duplicated `@adonisjs/core` and the container-free path, which has no\n // provider. A conflicting `federation` member is reported once; the\n // federation endpoints do not need the accessor.\n const contextClass = ctx.constructor as unknown as HttpContextClass;\n if (\n !ensureFederationGetter(contextClass) && !conflicting.has(contextClass)\n ) {\n conflicting.add(contextClass);\n ctx.logger.error(\n 'Something other than @fedify/adonisjs defines \"federation\" on this ' +\n 'application\\'s HttpContext, so \"ctx.federation\" is not the Fedify ' +\n \"request context. Federation endpoints are unaffected; rename the \" +\n \"conflicting macro or getter to use it from controllers\",\n );\n }\n\n // The ignore check and Fedify must see the same URL, so build it once.\n const { url, authority, unusableAuthority } = toRequestUrl(ctx.request);\n const path = url.pathname;\n\n if (\n this.#ignoreRoutePrefixes.some((prefix) => path.startsWith(prefix)) ||\n FORBIDDEN_METHODS.has(ctx.request.intended().toUpperCase())\n ) {\n // Opted out, or impossible to represent as a `Request`.\n setFederationContextFactory(ctx, () => {\n throw new E_FEDIFY_CONTEXT_UNAVAILABLE(path);\n });\n return next();\n }\n\n // Only past the bypass: on an opted-out path Fedify never sees the URL,\n // so the placeholder is harmless.\n if (unusableAuthority !== undefined) {\n warnUnusableAuthority(ctx.logger, unusableAuthority);\n }\n\n const request = toFetchRequest(ctx, url, authority);\n\n // `federation.fetch()` takes `contextData` by value, so the factory runs\n // before the router.\n const contextData = await this.#contextDataFactory(ctx);\n\n // Also for requests Fedify declines: a controller serving the HTML half of\n // a shared URL needs it. The getter creates the context lazily.\n setFederationContextFactory(\n ctx,\n () => federation.createContext(request, contextData),\n );\n\n /**\n * What happened while Fedify was deciding: whether a fall-through callback\n * handed the request to AdonisJS, and whether AdonisJS produced nothing of\n * its own so the 406 below is the answer. \"Nothing\" means no route\n * matched (`ctx.route` is set only on a match) *and* the framework's own\n * 404: a later server middleware (`@adonisjs/cors` on a preflight) or a\n * downstream failure can answer without the router running, and must not\n * be overwritten. A downstream middleware *deliberately* answering 404 on\n * a Fedify-owned path is indistinguishable from that default and is also\n * replaced by the 406.\n *\n * `router.match()` could say before `next()` whether a route exists and\n * spare the rollback, but is deliberately not asked:\n *\n * 1. Every integration follows the deferred-406 contract the manual\n * documents: delegate, and answer 406 only when the framework answered\n * 404 (Hono reads `ctx.res.status` after `next()`, Express `req.route`).\n * Deciding from the route table would give AdonisJS a narrower one.\n *\n * 2. Server middleware answers without a route by design (`@adonisjs/cors`\n * on a preflight, `@adonisjs/static`, a maintenance page), and Fedify\n * calls `onNotAcceptable` for a wildcard `Accept` too -- what curl and\n * a preflight send -- so skipping `next()` would cut those off on every\n * path Fedify owns.\n *\n * 3. `ctx.route` is the framework's own verdict. A lookup here would\n * duplicate `routeFinder`'s preamble and drift silently as the router\n * changes.\n *\n * The price is undoing a response AdonisJS was allowed to start: the\n * headers in {@link DISCARDED_ENTITY_HEADERS} are put back by name, a\n * pending body is dropped, and nothing is touched once the head has been\n * flushed.\n *\n * An object rather than two `let`s so TypeScript does not narrow them.\n */\n const outcome: { delegated: boolean; answer406: boolean } = {\n delegated: false,\n answer406: false,\n };\n\n const response = await federation.fetch(request, {\n contextData,\n\n /**\n * Fedify has no route for this path: hand the request to AdonisJS.\n * `await next()` resolves even on no match, since `E_ROUTE_NOT_FOUND`\n * goes through the exception handler and renders a 404.\n */\n onNotFound: async () => {\n outcome.delegated = true;\n await next();\n return delegatedResponse(ctx.response.getStatus());\n },\n\n /**\n * Fedify has a route but cannot satisfy `Accept` (a browser at an actor\n * URL). AdonisJS gets a chance to serve its own representation first;\n * 406 is only the answer if it has nothing either. `Accept` is merged\n * into `Vary` rather than written over it, and the snapshot's value is\n * merged back after `next()`, because a route may have replaced the\n * header meanwhile.\n */\n onNotAcceptable: async () => {\n outcome.delegated = true;\n mergeIntoVary(ctx, \"Accept\");\n // A value snapshot, not a live view: `getHeaders()` builds a fresh\n // object on every call.\n const headersBefore = ctx.response.getHeaders();\n await next();\n\n // Put back what `Vary` carried before `next()`, `Accept` included. A\n // superset only costs cache hits; a missing entry lets a cache mix the\n // two representations up.\n mergeIntoVary(ctx, String(headersBefore.vary ?? \"\"));\n\n const status = ctx.response.getStatus();\n // Not once the head is flushed: the 406 could not be written over it,\n // and `removeHeader()` would raise `ERR_HTTP_HEADERS_SENT`.\n outcome.answer406 = ctx.route === undefined && status === 404 &&\n !ctx.response.headersSent;\n\n if (outcome.answer406) rollBackDiscardedResponse(ctx, headersBefore);\n\n // The 406 itself is produced below; this only feeds Fedify's log.\n return delegatedResponse(outcome.answer406 ? 406 : status);\n },\n });\n\n // Fedify owns the path, cannot satisfy `Accept`, and AdonisJS had nothing\n // either. `Vary` already carries `Accept`.\n if (outcome.answer406) {\n // Before `send()`, which only sets the buffered *content*: a 404 page\n // rendered with `download()` or `stream()` would otherwise still go out,\n // or leak its readable.\n discardPendingBody(ctx);\n ctx.response\n .status(406)\n .type(\"text/plain\")\n .send(\"Not acceptable\");\n return;\n }\n\n if (outcome.delegated) {\n // AdonisJS produced the response (a matched route or its own 404 page).\n return;\n }\n\n writeFedifyResponse(response, ctx);\n }\n}\n\n/**\n * Creates a middleware class around a `Federation` the application built\n * itself, with no container or `config/fedify.ts` involved. This is the\n * package's default export; most applications use the provider instead.\n *\n * @example\n * ```ts\n * // start/kernel.ts\n * import fedifyMiddleware from '@fedify/adonisjs'\n * // A module of the application's own that default-exports its `Federation`.\n * import federation from '../app/federation.js'\n *\n * server.use([\n * async () => ({ default: fedifyMiddleware(federation, (ctx) => ctx) }),\n * ])\n * ```\n *\n * With the default `ContextData` of `HttpContext | null`, pass `(ctx) => ctx`\n * to match the provider; see {@link FedifyMiddlewareArgs}.\n *\n * @returns A {@link FedifyMiddleware} subclass whose constructor takes no\n * arguments, which is what `server.use()` constructs.\n * @param federation The federation instance to mount.\n * @param args The context-data factory and the options.\n */\nexport function fedifyMiddleware<TContextData>(\n federation: Federation<TContextData>,\n ...args: FedifyMiddlewareArgs<TContextData>\n): FedifyMiddlewareClass {\n // Here rather than in the constructor guard, which fires only on the first\n // request and blames the provider or a duplicated package copy; a factory\n // caller's likely mistake is a module that default-exports nothing.\n if (federation == null) {\n throw new E_MISSING_FEDERATION(\n \"fedifyMiddleware() was called without a Federation instance. Check \" +\n \"that the module its first argument comes from actually \" +\n \"default-exports the Federation it builds\",\n );\n }\n\n // Named so container errors and tracing show something readable.\n return class ConfiguredFedifyMiddleware\n extends FedifyMiddleware<TContextData> {\n constructor() {\n super(federation, ...args);\n }\n };\n}\n"],"mappings":"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"}