@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.
- package/LICENSE +20 -0
- package/README.md +420 -0
- package/dist/configure.cjs +108 -0
- package/dist/configure.cjs.map +1 -0
- package/dist/configure.d.cts +14 -0
- package/dist/configure.d.ts +14 -0
- package/dist/configure.js +107 -0
- package/dist/configure.js.map +1 -0
- package/dist/index.cjs +19 -0
- package/dist/index.d.cts +8 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.js +8 -0
- package/dist/package.cjs +121 -0
- package/dist/package.cjs.map +1 -0
- package/dist/package.js +115 -0
- package/dist/package.js.map +1 -0
- package/dist/providers/fedify_provider.cjs +270 -0
- package/dist/providers/fedify_provider.cjs.map +1 -0
- package/dist/providers/fedify_provider.d.cts +54 -0
- package/dist/providers/fedify_provider.d.ts +55 -0
- package/dist/providers/fedify_provider.js +269 -0
- package/dist/providers/fedify_provider.js.map +1 -0
- package/dist/services/builder.cjs +9 -0
- package/dist/services/builder.cjs.map +1 -0
- package/dist/services/builder.d.cts +7 -0
- package/dist/services/builder.d.ts +8 -0
- package/dist/services/builder.js +8 -0
- package/dist/services/builder.js.map +1 -0
- package/dist/src/builder.cjs +55 -0
- package/dist/src/builder.cjs.map +1 -0
- package/dist/src/builder.d.cts +14 -0
- package/dist/src/builder.d.ts +14 -0
- package/dist/src/builder.js +54 -0
- package/dist/src/builder.js.map +1 -0
- package/dist/src/define_config.cjs +114 -0
- package/dist/src/define_config.cjs.map +1 -0
- package/dist/src/define_config.d.cts +30 -0
- package/dist/src/define_config.d.ts +30 -0
- package/dist/src/define_config.js +113 -0
- package/dist/src/define_config.js.map +1 -0
- package/dist/src/errors.cjs +77 -0
- package/dist/src/errors.cjs.map +1 -0
- package/dist/src/errors.d.cts +56 -0
- package/dist/src/errors.d.ts +56 -0
- package/dist/src/errors.js +73 -0
- package/dist/src/errors.js.map +1 -0
- package/dist/src/fedify_middleware.cjs +507 -0
- package/dist/src/fedify_middleware.cjs.map +1 -0
- package/dist/src/fedify_middleware.d.cts +85 -0
- package/dist/src/fedify_middleware.d.ts +85 -0
- package/dist/src/fedify_middleware.js +505 -0
- package/dist/src/fedify_middleware.js.map +1 -0
- package/dist/src/http_context.cjs +79 -0
- package/dist/src/http_context.cjs.map +1 -0
- package/dist/src/http_context.d.cts +31 -0
- package/dist/src/http_context.d.ts +31 -0
- package/dist/src/http_context.js +76 -0
- package/dist/src/http_context.js.map +1 -0
- package/dist/src/logging.cjs +106 -0
- package/dist/src/logging.cjs.map +1 -0
- package/dist/src/logging.d.cts +45 -0
- package/dist/src/logging.d.ts +45 -0
- package/dist/src/logging.js +106 -0
- package/dist/src/logging.js.map +1 -0
- package/dist/src/types.cjs +1 -0
- package/dist/src/types.d.cts +269 -0
- package/dist/src/types.d.ts +269 -0
- package/dist/src/types.js +1 -0
- package/dist/src/unusable_authority.cjs +79 -0
- package/dist/src/unusable_authority.cjs.map +1 -0
- package/dist/src/unusable_authority.js +76 -0
- package/dist/src/unusable_authority.js.map +1 -0
- package/dist/stubs/config/fedify.stub +105 -0
- package/dist/stubs/federation/main.stub +64 -0
- package/dist/stubs/main.cjs +16 -0
- package/dist/stubs/main.cjs.map +1 -0
- package/dist/stubs/main.d.cts +11 -0
- package/dist/stubs/main.d.ts +11 -0
- package/dist/stubs/main.js +15 -0
- package/dist/stubs/main.js.map +1 -0
- package/dist/stubs/start/federation.stub +20 -0
- 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":";;;;;;;;;;;;;;;;;;AA2EA,MAAM,cAAc,IAAI,QAAgB;;;;AAKxC,MAAM,oBAAoB,IAAI,IAAI;CAAC;CAAW;CAAS;AAAO,CAAC;;;;;;;AAQ/D,SAAS,gBACP,SAC4B;CAC5B,IAAI;CAEJ,OAAO,IAAI,eACT;EACE,MAAM,KAAK,YAAY;GACrB,WAAY,SAAS,MAAM,OAAO,CAAC,CAChC,UAAU;GAEb,MAAM,EAAE,MAAM,UAAU,MAAM,OAAO,KAAK;GAC1C,IAAI,MAAM,WAAW,MAAM;QACtB,WAAW,QAAQ,KAAK;EAC/B;EACA,OAAO,QAAQ;GACb,OAAO,QAAQ,OAAO,MAAM;EAC9B;CACF,GACA,EAAE,eAAe,EAAE,CACrB;AACF;;;;;;AAOA,MAAM,gBAAgB;;;;;;;;;;;;;AActB,SAAS,mBAAmB,MAAsB;CAChD,MAAM,QAAQ,KAAK,QAAQ,GAAG;CAC9B,MAAM,aAAa,QAAQ,IAAI,OAAO,KAAK,MAAM,GAAG,KAAK,EAAC,CAAE,KAAK;CACjE,OAAO,cAAc,KAAK,SAAS,IAAI,KAAK;AAC9C;;;;;;;;AASA,SAAS,aAAa,QAAoC;CAGxD,IAAI,OAAO,WAAW,GAAG,GAAG,OAAO;CAMnC,MAAM,SAAS,uCAAuC,KAAK,MAAM;CAGjE,IAAI,WAAW,MAAM,OAAO;CAE5B,MAAM,OAAO,OAAO,MAAM,OAAO,EAAE,CAAC,MAAM;CAC1C,OAAO,KAAK,WAAW,GAAG,IAAI,OAAO,IAAI;AAC3C;;;;;;;AAQA,SAAS,gBACP,UACA,WACA,QACiB;CACjB,IAAI,cAAc,IAAI,OAAO;CAE7B,IAAI;EACF,OAAO,IAAI,IAAI,GAAG,SAAS,KAAK,YAAY,QAAQ;CACtD,QAAQ;EACN,OAAO;CACT;AACF;;;;;;;;;;;;;;;;;AAkBA,SAAS,aACP,SAC8D;CAG9D,MAAM,WAAW,QAAQ,SAAS,CAAC,CAAC,YAAY,MAAM,UAClD,UACA;CACJ,MAAM,OAAO,QAAQ,UAAU;CAC/B,MAAM,YAAY,QAAQ,OAAO,KAAK,mBAAmB,IAAI;CAG7D,MAAM,SAAS,aAAa,QAAQ,QAAQ,OAAO,GAAG,KAAK;CAE3D,MAAM,SAAS,gBAAgB,UAAU,WAAW,MAAM;CAC1D,IAAI,MAAM;CACV,IAAI;CACJ,IAAI,QAAQ,WAAW;EAGrB,MAAM,IAAI,IAAI,GAAG,SAAS,KAAK,mBAAmB,QAAQ;EAC1D,IAAI,QAAQ,MAAM,oBAAoB;CACxC;CAEA,OAAO;EACL;EACA,WAAW,WAAW,YAAY,YAAY;EAC9C;CACF;AACF;;;;;;;;;;AAWA,SAAS,eACP,KACA,KACA,WACS;CACT,MAAM,UAAU,IAAI;CACpB,MAAM,SAAS,QAAQ,SAAS,CAAC,CAAC,YAAY;CAE9C,MAAM,UAAU,IAAI,QAAQ;CAC5B,MAAM,aAAa,QAAQ,QAAQ;CAEnC,KAAK,MAAM,OAAO,OAAO,KAAK,UAAU,GAAG;EACzC,IAAI,IAAI,WAAW,GAAG,GAAG;EACzB,MAAM,QAAQ,WAAW;EACzB,IAAI,MAAM,QAAQ,KAAK,GAAG;GACxB,KAAK,MAAM,QAAQ,OAAO,QAAQ,OAAO,KAAK,IAAI;EACpD,OAAO,IAAI,OAAO,UAAU,UAAU;GACpC,QAAQ,OAAO,KAAK,KAAK;EAC3B;CACF;CASA,IAAI,cAAc,aAAa,CAAC,QAAQ,IAAI,MAAM,GAAG;EACnD,QAAQ,IAAI,QAAQ,SAAS;CAC/B;CAEA,MAAM,UAAU,WAAW,SAAS,WAAW;CAE/C,OAAO,IAAI,QAAQ,KAAK;EACtB;EACA;EACA,MAAM,UAAU,gBAAgB,QAAQ,OAAO,IAAI;EAEnD,GAAG,EAAE,QAAQ,OAAO;CACtB,CAAC;AACH;;;;;;;;AASA,SAAS,oBAAoB,UAAoB,KAAwB;CACvE,IAAI,SAAS,OAAO,SAAS,MAAM;CAEnC,KAAK,MAAM,CAAC,KAAK,UAAU,SAAS,SAAS;EAC3C,MAAM,OAAO,IAAI,YAAY;EAC7B,IAAI,SAAS,cAAc;EAC3B,IAAI,SAAS,QAAQ;GACnB,cAAc,KAAK,KAAK;GACxB;EACF;EACA,IAAI,SAAS,OAAO,KAAK,KAAK;CAChC;CAEA,KAAK,MAAM,UAAU,SAAS,QAAQ,aAAa,GAAG;EACpD,IAAI,SAAS,OAAO,cAAc,MAAM;CAC1C;CAEA,MAAM,WAAW,SAAS,SAAS,QACjC,SAAS,WAAW,OACpB,SAAS,WAAW,OAEpB,IAAI,QAAQ,SAAS,CAAC,CAAC,YAAY,MAAM;CAE3C,IAAI,UAAU;EAGZ,SAAS,MAAM,OAAO,CAAC,CAAC,YAAY,CAAC,CAAC;EACtC;CACF;CAEA,IAAI,SAAS,OAAO,SAAS,OAAQ,UAAU;EAC7C,IAAI,OAAO,MAAM,EAAE,KAAK,MAAM,GAAG,sCAAsC;EACvE,OAAO,CAAC,yBAAyB,GAAG;CACtC,CAAC;AACH;;;;AAKA,SAAS,UAAU,OAAyB;CAC1C,OAAO,MACJ,MAAM,GAAG,CAAC,CACV,KAAK,UAAU,MAAM,KAAK,CAAC,CAAC,CAC5B,QAAQ,UAAU,UAAU,EAAE;AACnC;;;;;;;;;;;;;;;;;AAkBA,SAAS,cAAc,KAAkB,UAAwB;CAC/D,MAAM,OAAiB,CAAC;CACxB,MAAM,SAAS;EACb,OAAO,IAAI,SAAS,UAAU,MAAM,KAAK,EAAE;EAC3C,OAAO,IAAI,SAAS,SAAS,UAAU,MAAM,KAAK,EAAE;EACpD;CACF;CAEA,KAAK,MAAM,SAAS,QAAQ;EAC1B,KAAK,MAAM,SAAS,UAAU,KAAK,GAAG;GACpC,IAAI,UAAU,KAAK;IACjB,IAAI,SAAS,OAAO,QAAQ,GAAG;IAC/B;GACF;GACA,IAAI,CAAC,KAAK,MAAM,MAAM,EAAE,YAAY,MAAM,MAAM,YAAY,CAAC,GAAG;IAC9D,KAAK,KAAK,KAAK;GACjB;EACF;CACF;CAEA,IAAI,KAAK,SAAS,GAAG,IAAI,SAAS,OAAO,QAAQ,KAAK,KAAK,IAAI,CAAC;AAClE;;;;;;;;;;;AAYA,MAAM,2BAAgD,IAAI,IAAI;CAC5D;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;AACF,CAAC;;;;;;;;AASD,SAAS,qBAAqB,KAAkB,KAAmB;CAEjE,IAAI,SAAS,OAAO,KAAK,GAAG;CAC5B,IAAI,SAAS,aAAa,GAAG;AAC/B;;;;;;;;;;;AAYA,SAAS,mBAAmB,KAAwB;CAClD,MAAM,SAAS,IAAI,SAAS;CAC5B,IAAI,SAAS,WAAW,CAAC;CACzB,IAAI,UAAU,MAAM;CAGpB,IAAI,aAAa,QAAQ;EAGvB,OAAO,GAAG,eAAe,CAAC,CAAC;EAC3B,IAAI,CAAC,OAAO,WAAW,OAAO,QAAQ;CACxC,OAAO,IAAI,CAAC,OAAO,QAAQ;EAEzB,OAAO,OAAO,CAAC,CAAC,YAAY,CAAC,CAAC;CAChC;AACF;;;;;;;;;;;;;;;;;AAkBA,SAAS,0BACP,KACA,eACM;CACN,MAAM,eAAe,IAAI,SAAS,WAAW;CAE7C,KAAK,MAAM,OAAO,0BAA0B;EAE1C,MAAM,SAAS,OAAO,OAAO,eAAe,GAAG,IAC3C,cAAc,OACd;EAIJ,IAAI,WAAW,WAAW,IAAI,SAAS,OAAO,KAAK,MAAM;OACpD,IAAI,OAAO,OAAO,cAAc,GAAG,GAAG,qBAAqB,KAAK,GAAG;CAC1E;AACF;;;;;;;AAQA,SAAS,kBAAkB,QAA0B;CACnD,OAAO,IAAI,SAAS,MAAM,EACxB,QAAQ,UAAU,OAAO,UAAU,MAAM,SAAS,IACpD,CAAC;AACH;;;;;;;;;;AAWA,IAAqB,mBAArB,MACqC;CACnC;CACA;CACA;;;;;;;CAQA,YACE,YACA,GAAG,MACH;EAIA,IAAI,cAAc,MAAM,MAAM,IAAI,qBAAqB;EAGvD,MAAM,CAAC,oBAAoB,WAAW;EAKtC,KAAKA,cAAc;EACnB,KAAKC,sBAAsB,6BAClB;EACT,KAAKC,uBAAuB,SAAS,uBAAuB,CAAC;CAC/D;CAEA,MAAM,OAAO,KAAkB,MAA6B;EAC1D,MAAM,aAAa,KAAKF;EAMxB,MAAM,eAAe,IAAI;EACzB,IACE,CAAC,uBAAuB,YAAY,KAAK,CAAC,YAAY,IAAI,YAAY,GACtE;GACA,YAAY,IAAI,YAAY;GAC5B,IAAI,OAAO,MACT,0EACE,wEACA,sEACA,wDACJ;EACF;EAGA,MAAM,EAAE,KAAK,WAAW,sBAAsB,aAAa,IAAI,OAAO;EACtE,MAAM,OAAO,IAAI;EAEjB,IACE,KAAKE,qBAAqB,MAAM,WAAW,KAAK,WAAW,MAAM,CAAC,KAClE,kBAAkB,IAAI,IAAI,QAAQ,SAAS,CAAC,CAAC,YAAY,CAAC,GAC1D;GAEA,4BAA4B,WAAW;IACrC,MAAM,IAAI,6BAA6B,IAAI;GAC7C,CAAC;GACD,OAAO,KAAK;EACd;EAIA,IAAI,sBAAsB,WAAW;GACnC,sBAAsB,IAAI,QAAQ,iBAAiB;EACrD;EAEA,MAAM,UAAU,eAAe,KAAK,KAAK,SAAS;EAIlD,MAAM,cAAc,MAAM,KAAKD,oBAAoB,GAAG;EAItD,4BACE,WACM,WAAW,cAAc,SAAS,WAAW,CACrD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAsCA,MAAM,UAAsD;GAC1D,WAAW;GACX,WAAW;EACb;EAEA,MAAM,WAAW,MAAM,WAAW,MAAM,SAAS;GAC/C;;;;;;GAOA,YAAY,YAAY;IACtB,QAAQ,YAAY;IACpB,MAAM,KAAK;IACX,OAAO,kBAAkB,IAAI,SAAS,UAAU,CAAC;GACnD;;;;;;;;;GAUA,iBAAiB,YAAY;IAC3B,QAAQ,YAAY;IACpB,cAAc,KAAK,QAAQ;IAG3B,MAAM,gBAAgB,IAAI,SAAS,WAAW;IAC9C,MAAM,KAAK;IAKX,cAAc,KAAK,OAAO,cAAc,QAAQ,EAAE,CAAC;IAEnD,MAAM,SAAS,IAAI,SAAS,UAAU;IAGtC,QAAQ,YAAY,IAAI,UAAU,aAAa,WAAW,OACxD,CAAC,IAAI,SAAS;IAEhB,IAAI,QAAQ,WAAW,0BAA0B,KAAK,aAAa;IAGnE,OAAO,kBAAkB,QAAQ,YAAY,MAAM,MAAM;GAC3D;EACF,CAAC;EAID,IAAI,QAAQ,WAAW;GAIrB,mBAAmB,GAAG;GACtB,IAAI,SACD,OAAO,GAAG,CAAC,CACX,KAAK,YAAY,CAAC,CAClB,KAAK,gBAAgB;GACxB;EACF;EAEA,IAAI,QAAQ,WAAW;GAErB;EACF;EAEA,oBAAoB,UAAU,GAAG;CACnC;AACF;;;;;;;;;;;;;;;;;;;;;;;;;;AA2BA,SAAgB,iBACd,YACA,GAAG,MACoB;CAIvB,IAAI,cAAc,MAAM;EACtB,MAAM,IAAI,qBACR,wEACE,4DACA,0CACJ;CACF;CAGA,OAAO,MAAM,mCACH,iBAA+B;EACvC,cAAc;GACZ,MAAM,YAAY,GAAG,IAAI;EAC3B;CACF;AACF"}
|