@klnap/next-proxy-chain 1.0.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/dist/index.cjs ADDED
@@ -0,0 +1,882 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/index.ts
21
+ var index_exports = {};
22
+ __export(index_exports, {
23
+ DEFAULT_BLOCKED_REQUEST_HEADERS: () => DEFAULT_BLOCKED_REQUEST_HEADERS,
24
+ NEXT: () => NEXT,
25
+ RuleTimeoutError: () => RuleTimeoutError,
26
+ applyMergedToResponse: () => applyMergedToResponse,
27
+ assertIncludeExcludeNoOverlap: () => assertIncludeExcludeNoOverlap,
28
+ assertOverrideMechanismSupported: () => assertOverrideMechanismSupported,
29
+ compileScopes: () => compileScopes,
30
+ cookieItemsFromHeaders: () => cookieItemsFromHeaders,
31
+ createConsoleLogger: () => createConsoleLogger,
32
+ createProxyContext: () => createProxyContext,
33
+ decodeOverriddenRequestHeaders: () => decodeOverriddenRequestHeaders,
34
+ defineProxy: () => defineProxy,
35
+ formatPipelineLine: () => formatPipelineLine,
36
+ headersDiffer: () => headersDiffer,
37
+ host: () => host,
38
+ isNextResult: () => isNextResult,
39
+ isRequestHeaderAllowed: () => isRequestHeaderAllowed,
40
+ listSetCookie: () => listSetCookie,
41
+ mergeRequestHeaderOverrides: () => mergeRequestHeaderOverrides,
42
+ mergeWrites: () => mergeWrites,
43
+ next: () => next,
44
+ normalizeHost: () => normalizeHost,
45
+ parseCookieName: () => parseCookieName,
46
+ path: () => path,
47
+ pathMatches: () => pathMatches,
48
+ proxyChain: () => proxyChain,
49
+ responseHeaderItems: () => responseHeaderItems,
50
+ ruleLabel: () => ruleLabel,
51
+ ruleMatches: () => ruleMatches,
52
+ safeInternalError: () => safeInternalError,
53
+ toPathRegex: () => toPathRegex,
54
+ toRule: () => toRule,
55
+ toStatelessRegExp: () => toStatelessRegExp,
56
+ withPaths: () => withPaths,
57
+ withRuleTimeout: () => withRuleTimeout
58
+ });
59
+ module.exports = __toCommonJS(index_exports);
60
+ var import_server = require("next/server");
61
+
62
+ // src/core.ts
63
+ var NEXT = /* @__PURE__ */ Symbol.for("proxy-chain.next");
64
+ function next(options = {}) {
65
+ return { [NEXT]: true, ...options };
66
+ }
67
+ function isNextResult(value) {
68
+ return typeof value === "object" && value !== null && NEXT in value;
69
+ }
70
+ function createProxyContext() {
71
+ return /* @__PURE__ */ new Map();
72
+ }
73
+ var USELESS_FN_NAMES = /* @__PURE__ */ new Set(["", "proxy", "anonymous", "run", "next"]);
74
+ function usefulName(value) {
75
+ if (!value || USELESS_FN_NAMES.has(value)) return void 0;
76
+ return value;
77
+ }
78
+ function toStatelessRegExp(re) {
79
+ const flags = re.flags.replace(/[gy]/g, "");
80
+ return flags === re.flags ? re : new RegExp(re.source, flags);
81
+ }
82
+ function escapeRegex(value) {
83
+ return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
84
+ }
85
+ function host(matcher) {
86
+ const list = Array.isArray(matcher) ? matcher : [matcher];
87
+ const parts = list.map((m) => {
88
+ if (m instanceof RegExp) return `(?:${toStatelessRegExp(m).source})`;
89
+ return `(?:${escapeRegex(m.toLowerCase())})`;
90
+ });
91
+ return toStatelessRegExp(new RegExp(`^(?:${parts.join("|")})$`, "i"));
92
+ }
93
+ function isScopeObject(value) {
94
+ return "host" in value || "path" in value;
95
+ }
96
+ function compileScopes(input, pathOptions) {
97
+ if (input == null) return void 0;
98
+ const list = Array.isArray(input) ? input : [input];
99
+ const compilePath = (matcher) => {
100
+ if (Array.isArray(matcher)) {
101
+ const parts = matcher.map(
102
+ (m) => typeof m === "string" ? path(m, pathOptions) : toStatelessRegExp(m)
103
+ );
104
+ if (parts.length === 1) return parts[0];
105
+ return toStatelessRegExp(new RegExp(`(?:${parts.map((r) => `(?:${r.source})`).join("|")})`));
106
+ }
107
+ return typeof matcher === "string" ? path(matcher, pathOptions) : toStatelessRegExp(matcher);
108
+ };
109
+ const compiled = [];
110
+ for (const item of list) {
111
+ if (typeof item === "string" || item instanceof RegExp) {
112
+ compiled.push({ path: compilePath(item) });
113
+ continue;
114
+ }
115
+ if (item && typeof item === "object" && isScopeObject(item)) {
116
+ if (item.host == null && item.path == null) {
117
+ throw new Error("[proxy-chain] scope needs at least `host` or `path`");
118
+ }
119
+ compiled.push({
120
+ host: item.host != null ? host(item.host) : void 0,
121
+ path: item.path != null ? compilePath(item.path) : void 0
122
+ });
123
+ continue;
124
+ }
125
+ throw new Error("[proxy-chain] invalid include/exclude scope");
126
+ }
127
+ return compiled;
128
+ }
129
+ function scopeMatches(pathname, hostname, scope) {
130
+ if (scope.host) {
131
+ if (!hostname || !scope.host.test(hostname)) return false;
132
+ }
133
+ if (scope.path) {
134
+ if (!scope.path.test(pathname)) return false;
135
+ }
136
+ return true;
137
+ }
138
+ function toRule(entry) {
139
+ if (typeof entry === "function") {
140
+ const name2 = usefulName(entry.name);
141
+ return name2 ? { run: entry, name: name2 } : { run: entry };
142
+ }
143
+ const rule = entry;
144
+ const runName = typeof rule.run === "function" ? usefulName(rule.run.name) : void 0;
145
+ const name = usefulName(rule.name) ?? runName;
146
+ const harden = (scopes) => scopes?.map((s) => ({
147
+ host: s.host ? toStatelessRegExp(s.host) : void 0,
148
+ path: s.path ? toStatelessRegExp(s.path) : void 0
149
+ }));
150
+ return {
151
+ ...rule,
152
+ name: name ?? void 0,
153
+ include: harden(rule.include),
154
+ exclude: harden(rule.exclude)
155
+ };
156
+ }
157
+ function ruleLabel(rule, index) {
158
+ const runName = typeof rule.run === "function" ? usefulName(rule.run.name) : void 0;
159
+ return usefulName(rule.name) ?? runName ?? `rule#${index}`;
160
+ }
161
+ function withPaths(entry, filter) {
162
+ const base = toRule(entry);
163
+ const opts = filter.pathOptions;
164
+ const include = compileScopes(filter.include, opts);
165
+ const exclude = compileScopes(filter.exclude, opts);
166
+ const name = filter.name ?? base.name;
167
+ assertIncludeExcludeNoOverlap(include, exclude, name ?? "proxy");
168
+ return { run: base.run, include, exclude, name };
169
+ }
170
+ function ruleMatches(pathname, hostname, rule) {
171
+ if (rule.exclude?.some((scope) => scopeMatches(pathname, hostname, scope))) return false;
172
+ if (rule.include && !rule.include.some((scope) => scopeMatches(pathname, hostname, scope))) {
173
+ return false;
174
+ }
175
+ return true;
176
+ }
177
+ function pathMatches(pathname, rule) {
178
+ return ruleMatches(pathname, null, rule);
179
+ }
180
+ function normalizeHost(raw) {
181
+ if (!raw) return null;
182
+ const first = raw.split(",")[0]?.trim().toLowerCase();
183
+ if (!first) return null;
184
+ if (first.startsWith("[")) {
185
+ const end = first.indexOf("]");
186
+ return end === -1 ? first : first.slice(1, end);
187
+ }
188
+ const withoutPort = first.replace(/:\d+$/, "");
189
+ return withoutPort || null;
190
+ }
191
+ function parseCookieName(setCookie) {
192
+ const eq = setCookie.indexOf("=");
193
+ return (eq === -1 ? setCookie : setCookie.slice(0, eq)).trim();
194
+ }
195
+ function listSetCookie(headers) {
196
+ const extended = headers;
197
+ if (typeof extended.getSetCookie === "function") return extended.getSetCookie();
198
+ const single = headers.get("set-cookie");
199
+ return single ? [single] : [];
200
+ }
201
+ function cookieItemsFromHeaders(headers) {
202
+ const map = /* @__PURE__ */ new Map();
203
+ for (const value of listSetCookie(headers)) {
204
+ map.set(parseCookieName(value), value);
205
+ }
206
+ return map;
207
+ }
208
+ function responseHeaderItems(headers) {
209
+ const map = /* @__PURE__ */ new Map();
210
+ headers.forEach((value, key) => {
211
+ const lower = key.toLowerCase();
212
+ if (lower === "set-cookie") return;
213
+ if (lower.startsWith("x-middleware-next") || lower.startsWith("x-middleware-rewrite") || lower.startsWith("x-middleware-override-headers") || lower.startsWith("x-middleware-request-")) {
214
+ return;
215
+ }
216
+ map.set(key, value);
217
+ });
218
+ return map;
219
+ }
220
+ function decodeOverriddenRequestHeaders(headers) {
221
+ const result = new Headers();
222
+ const overrideHeaderNames = headers.get("x-middleware-override-headers");
223
+ if (overrideHeaderNames) {
224
+ for (const name of overrideHeaderNames.split(",")) {
225
+ const trimmed = name.trim();
226
+ const val = headers.get(`x-middleware-request-${trimmed}`);
227
+ if (val !== null) {
228
+ result.set(trimmed, val);
229
+ }
230
+ }
231
+ }
232
+ return result;
233
+ }
234
+ function mergeWrites(writes, strategy, onConflict) {
235
+ const resolved = /* @__PURE__ */ new Map();
236
+ const setBy = /* @__PURE__ */ new Map();
237
+ for (const write of writes) {
238
+ for (const [key, value] of write.items) {
239
+ const existing = resolved.get(key);
240
+ if (existing === void 0) {
241
+ resolved.set(key, value);
242
+ setBy.set(key, write.ruleName);
243
+ continue;
244
+ }
245
+ const previousRule = setBy.get(key);
246
+ if (typeof strategy === "function") {
247
+ const result = strategy(key, existing, value);
248
+ if (result === null) resolved.delete(key);
249
+ else resolved.set(key, result);
250
+ setBy.set(key, write.ruleName);
251
+ continue;
252
+ }
253
+ if (strategy === "first-write-wins") {
254
+ onConflict?.(key, previousRule, write.ruleName);
255
+ continue;
256
+ }
257
+ onConflict?.(key, write.ruleName, previousRule);
258
+ resolved.set(key, value);
259
+ setBy.set(key, write.ruleName);
260
+ }
261
+ }
262
+ return resolved;
263
+ }
264
+ function applyMergedToResponse(res, headers, cookies) {
265
+ for (const [key, value] of headers) {
266
+ res.headers.set(key, value);
267
+ }
268
+ res.headers.delete("set-cookie");
269
+ for (const cookie of cookies.values()) {
270
+ res.headers.append("set-cookie", cookie);
271
+ }
272
+ }
273
+ var DEFAULT_BLOCKED_REQUEST_HEADERS = [
274
+ "host",
275
+ "connection",
276
+ "keep-alive",
277
+ "proxy-authenticate",
278
+ "proxy-authorization",
279
+ "te",
280
+ "trailer",
281
+ "transfer-encoding",
282
+ "upgrade",
283
+ "content-length",
284
+ "cookie",
285
+ "authorization",
286
+ "x-forwarded-host",
287
+ "x-forwarded-for",
288
+ "x-forwarded-proto",
289
+ "x-real-ip",
290
+ "x-middleware-next",
291
+ "x-middleware-override-headers"
292
+ ];
293
+ function normalizeHeaderName(name) {
294
+ return name.toLowerCase();
295
+ }
296
+ function isRequestHeaderAllowed(name, policy = {}) {
297
+ const key = normalizeHeaderName(name);
298
+ const allowed = new Set((policy.allowed ?? []).map(normalizeHeaderName));
299
+ if (allowed.has(key)) return true;
300
+ if (key.startsWith("x-middleware-")) return false;
301
+ const blocked = /* @__PURE__ */ new Set([
302
+ ...DEFAULT_BLOCKED_REQUEST_HEADERS.map(normalizeHeaderName),
303
+ ...(policy.blocked ?? []).map(normalizeHeaderName)
304
+ ]);
305
+ return !blocked.has(key);
306
+ }
307
+ function mergeRequestHeaderOverrides(target, overrides, policy = {}) {
308
+ const dropped = [];
309
+ let applied = 0;
310
+ overrides.forEach((value, key) => {
311
+ if (!isRequestHeaderAllowed(key, policy)) {
312
+ dropped.push(key);
313
+ return;
314
+ }
315
+ target.set(key, value);
316
+ applied++;
317
+ });
318
+ return { applied, dropped };
319
+ }
320
+ function headersInitToHeaders(init) {
321
+ return new Headers(init);
322
+ }
323
+ function headersDiffer(base, overrides) {
324
+ for (const [key, value] of overrides.entries()) {
325
+ if (base.get(key) !== value) return true;
326
+ }
327
+ return false;
328
+ }
329
+ function segmentToRegex(segment) {
330
+ if (segment === "**") return "(?:.*)";
331
+ if (segment === "*") return "[^/]+";
332
+ if (segment.startsWith(":")) return "[^/]+";
333
+ return escapeRegex(segment);
334
+ }
335
+ function path(pattern, options = {}) {
336
+ const { locales = [] } = options;
337
+ const localePrefix = locales.length > 0 ? `(?:(?:${locales.map(escapeRegex).join("|")})\\/)?` : "";
338
+ if (pattern instanceof RegExp) {
339
+ const body2 = pattern.source.replace(/^\^/, "").replace(/\$$/, "");
340
+ const needsStart = !pattern.source.startsWith("^");
341
+ const needsEnd = !pattern.source.endsWith("$");
342
+ return toStatelessRegExp(
343
+ new RegExp(
344
+ `${needsStart ? "^" : ""}${localePrefix ? `/${localePrefix}` : ""}${body2}${needsEnd ? "$" : ""}`,
345
+ pattern.flags
346
+ )
347
+ );
348
+ }
349
+ const normalized = (pattern.startsWith("/") ? pattern : `/${pattern}`).replace(/\/$/, "") || "/";
350
+ const segments = normalized.slice(1).split("/");
351
+ const trailingStar = segments.length > 0 && segments[segments.length - 1] === "*";
352
+ const suffix = trailingStar ? "(?:\\/.*)?$" : "$";
353
+ const body = trailingStar ? segments.slice(0, -1).map(segmentToRegex).concat("[^/]+").join("\\/") : segments.map(segmentToRegex).join("\\/");
354
+ return new RegExp(`^/${localePrefix}${body}${suffix}`);
355
+ }
356
+ function toPathRegex(matcher, options) {
357
+ const list = Array.isArray(matcher) ? matcher : [matcher];
358
+ const regexes = list.map((m) => path(m, options));
359
+ if (regexes.length === 1) return toStatelessRegExp(regexes[0]);
360
+ return toStatelessRegExp(new RegExp(`(?:${regexes.map((r) => `(?:${r.source})`).join("|")})`));
361
+ }
362
+ function scopeKey(scope) {
363
+ return `${scope.host?.source ?? "*"}::${scope.host?.flags ?? ""}|${scope.path?.source ?? "*"}::${scope.path?.flags ?? ""}`;
364
+ }
365
+ function assertIncludeExcludeNoOverlap(include, exclude, label) {
366
+ if (!include?.length || !exclude?.length) return;
367
+ const excluded = new Set(exclude.map(scopeKey));
368
+ for (const scope of include) {
369
+ if (excluded.has(scopeKey(scope))) {
370
+ throw new Error(
371
+ `[proxy-chain] ${label}: include and exclude share an identical scope. Remove one.`
372
+ );
373
+ }
374
+ }
375
+ }
376
+ var LEVEL_RANK = {
377
+ debug: 10,
378
+ info: 20,
379
+ warn: 30,
380
+ error: 40
381
+ };
382
+ function createConsoleLogger(minLevel = "info") {
383
+ const min = LEVEL_RANK[minLevel];
384
+ const write = (level) => (obj, msg) => {
385
+ if (LEVEL_RANK[level] < min) return;
386
+ if (typeof obj === "string") {
387
+ console[level](obj);
388
+ return;
389
+ }
390
+ const message = msg ?? "";
391
+ if (message) console[level](`[proxy-chain] ${message}`, obj);
392
+ else console[level]("[proxy-chain]", obj);
393
+ };
394
+ return {
395
+ debug: write("debug"),
396
+ info: write("info"),
397
+ warn: write("warn"),
398
+ error: write("error")
399
+ };
400
+ }
401
+ function formatDuration(ms) {
402
+ return `${ms.toFixed(2)}ms`;
403
+ }
404
+ function formatPipelineLine(input) {
405
+ const trace = input.traceId ? ` [${input.traceId.slice(0, 8)}]` : "";
406
+ const req = `${input.method} ${input.path}${trace}`;
407
+ const { name, outcome } = input;
408
+ switch (outcome.kind) {
409
+ case "passed":
410
+ return `[proxy-chain] \xB7 ${req} | ${name} (${formatDuration(outcome.duration)}) -> passed`;
411
+ case "next":
412
+ return `[proxy-chain] ~ ${req} | ${name} (${formatDuration(outcome.duration)}) -> headers mutated (${outcome.headers})`;
413
+ case "stop":
414
+ return `[proxy-chain] \xD7 ${req} | ${name} (${formatDuration(outcome.duration)}) -> stop ${outcome.status}`;
415
+ }
416
+ }
417
+ var RuleTimeoutError = class extends Error {
418
+ ruleName;
419
+ timeoutMs;
420
+ constructor(ruleName, timeoutMs) {
421
+ super(`[proxy-chain] ${ruleName} timed out after ${timeoutMs}ms`);
422
+ this.name = "RuleTimeoutError";
423
+ this.ruleName = ruleName;
424
+ this.timeoutMs = timeoutMs;
425
+ }
426
+ };
427
+ async function withRuleTimeout(promise, timeoutMs, ruleName) {
428
+ if (!timeoutMs || timeoutMs <= 0) return promise;
429
+ let timer;
430
+ const timeout = new Promise((_, reject) => {
431
+ timer = setTimeout(() => reject(new RuleTimeoutError(ruleName, timeoutMs)), timeoutMs);
432
+ });
433
+ try {
434
+ return await Promise.race([promise, timeout]);
435
+ } finally {
436
+ if (timer !== void 0) clearTimeout(timer);
437
+ }
438
+ }
439
+
440
+ // src/index.ts
441
+ function isPathFilter(value) {
442
+ if (value === null || typeof value !== "object") return false;
443
+ if ("nextUrl" in value) return false;
444
+ return "include" in value || "exclude" in value || "name" in value || "pathOptions" in value;
445
+ }
446
+ function defineProxy(nameOrRun, maybeRun) {
447
+ const defaultName = typeof nameOrRun === "string" ? nameOrRun : void 0;
448
+ const run = typeof nameOrRun === "function" ? nameOrRun : maybeRun;
449
+ const proxy = ((reqOrFilter, eventOrCtx, maybeEvent) => {
450
+ if (isPathFilter(reqOrFilter)) {
451
+ return withPaths(run, {
452
+ ...reqOrFilter,
453
+ name: reqOrFilter.name ?? defaultName
454
+ });
455
+ }
456
+ return run(reqOrFilter, eventOrCtx, maybeEvent);
457
+ });
458
+ Object.defineProperty(proxy, "name", {
459
+ value: defaultName ?? "",
460
+ configurable: true
461
+ });
462
+ return proxy;
463
+ }
464
+ function resolveConfig(overrides = {}) {
465
+ const { logger, debug, ...rest } = overrides;
466
+ return {
467
+ createContext: () => createProxyContext(),
468
+ cookieMergeStrategy: "last-write-wins",
469
+ headerMergeStrategy: "last-write-wins",
470
+ debug: false,
471
+ ruleTimeoutMs: 0,
472
+ requestHeaderPolicy: {},
473
+ strictOverrideCheck: false,
474
+ ...rest,
475
+ ...debug !== void 0 ? { debug } : {},
476
+ logger: logger ?? createConsoleLogger(debug ? "debug" : "info")
477
+ };
478
+ }
479
+ function safeInternalError() {
480
+ return new import_server.NextResponse("Internal Server Error", {
481
+ status: 500,
482
+ headers: { "content-type": "text/plain; charset=utf-8" }
483
+ });
484
+ }
485
+ var cachedOverrideSupported = null;
486
+ function assertOverrideMechanismSupported(strict = false) {
487
+ if (cachedOverrideSupported !== null) return cachedOverrideSupported;
488
+ try {
489
+ const testRes = import_server.NextResponse.next({
490
+ request: { headers: new Headers({ "x-proxy-chain-probe": "1" }) }
491
+ });
492
+ const isSupported = testRes.headers.get("x-middleware-next") === "1";
493
+ cachedOverrideSupported = isSupported;
494
+ if (!isSupported) {
495
+ const msg = "[proxy-chain] Warning: platform NextResponse.next() does not support x-middleware-next header overrides.";
496
+ if (strict) throw new Error(msg);
497
+ console.warn(msg);
498
+ }
499
+ return isSupported;
500
+ } catch (err) {
501
+ cachedOverrideSupported = false;
502
+ const msg = "[proxy-chain] Warning: unable to verify NextResponse request override mechanism.";
503
+ if (strict) throw err instanceof Error ? err : new Error(msg);
504
+ console.warn(msg, err);
505
+ return false;
506
+ }
507
+ }
508
+ function resolveRuleResult(res, requestHeaderPolicy, logger, debug) {
509
+ if (res == null) {
510
+ return { kind: "continue", responseHeaders: /* @__PURE__ */ new Map(), cookies: /* @__PURE__ */ new Map() };
511
+ }
512
+ if (isNextResult(res)) {
513
+ let request;
514
+ if (res.request) {
515
+ const filtered = new Headers();
516
+ const { dropped } = mergeRequestHeaderOverrides(
517
+ filtered,
518
+ headersInitToHeaders(res.request),
519
+ requestHeaderPolicy
520
+ );
521
+ if (debug && dropped.length) {
522
+ logger.warn({ dropped }, "blocked request header mutation");
523
+ }
524
+ if ([...filtered.keys()].length) request = filtered;
525
+ }
526
+ return {
527
+ kind: "continue",
528
+ request,
529
+ responseHeaders: res.headers ? responseHeaderItems(headersInitToHeaders(res.headers)) : /* @__PURE__ */ new Map(),
530
+ cookies: /* @__PURE__ */ new Map()
531
+ };
532
+ }
533
+ if (res.headers.get("x-middleware-next") === "1") {
534
+ let request;
535
+ const decoded = decodeOverriddenRequestHeaders(res.headers);
536
+ if ([...decoded.keys()].length > 0) {
537
+ const target = new Headers();
538
+ mergeRequestHeaderOverrides(target, decoded, requestHeaderPolicy);
539
+ if ([...target.keys()].length) request = target;
540
+ }
541
+ return {
542
+ kind: "continue",
543
+ request,
544
+ responseHeaders: responseHeaderItems(res.headers),
545
+ cookies: cookieItemsFromHeaders(res.headers)
546
+ };
547
+ }
548
+ const rewriteHeader = res.headers.get("x-middleware-rewrite");
549
+ if (rewriteHeader && (res.status === 200 || !res.status)) {
550
+ let request;
551
+ const decoded = decodeOverriddenRequestHeaders(res.headers);
552
+ if ([...decoded.keys()].length > 0) {
553
+ const target = new Headers();
554
+ mergeRequestHeaderOverrides(target, decoded, requestHeaderPolicy);
555
+ if ([...target.keys()].length) request = target;
556
+ }
557
+ return {
558
+ kind: "continue",
559
+ request,
560
+ responseHeaders: responseHeaderItems(res.headers),
561
+ cookies: cookieItemsFromHeaders(res.headers),
562
+ rewriteUrl: rewriteHeader
563
+ };
564
+ }
565
+ return { kind: "stop", response: res };
566
+ }
567
+ function proxyChain(optionsOrEntries, maybeEntries) {
568
+ const options = Array.isArray(optionsOrEntries) ? {} : optionsOrEntries;
569
+ const entries = Array.isArray(optionsOrEntries) ? optionsOrEntries : maybeEntries;
570
+ const config = resolveConfig(options);
571
+ const rules = entries.map(toRule);
572
+ const {
573
+ logger,
574
+ debug,
575
+ cookieMergeStrategy,
576
+ headerMergeStrategy,
577
+ ruleTimeoutMs,
578
+ requestHeaderPolicy,
579
+ strictOverrideCheck
580
+ } = config;
581
+ assertOverrideMechanismSupported(strictOverrideCheck);
582
+ for (let i = 0; i < rules.length; i++) {
583
+ assertIncludeExcludeNoOverlap(rules[i].include, rules[i].exclude, ruleLabel(rules[i], i));
584
+ }
585
+ return async function proxy(req, event) {
586
+ let ctx;
587
+ try {
588
+ ctx = config.createContext(req, event);
589
+ } catch (err) {
590
+ logger.error({ err }, "createContext threw");
591
+ return safeInternalError();
592
+ }
593
+ const method = req.method;
594
+ const pathname = req.nextUrl.pathname;
595
+ const traceId = req.headers.get("x-request-id") ?? req.headers.get("traceparent") ?? req.headers.get("x-b3-traceid") ?? void 0;
596
+ let currentReq = req;
597
+ let pendingRequestOverrides = null;
598
+ let pendingRewriteUrl = null;
599
+ const headerWrites = [];
600
+ const cookieWrites = [];
601
+ const materializeRequest = () => {
602
+ if (!pendingRequestOverrides) return currentReq;
603
+ if (!headersDiffer(currentReq.headers, pendingRequestOverrides)) {
604
+ pendingRequestOverrides = null;
605
+ return currentReq;
606
+ }
607
+ const merged = new Headers(currentReq.headers);
608
+ pendingRequestOverrides.forEach((v, k) => {
609
+ merged.set(k, v);
610
+ });
611
+ pendingRequestOverrides = null;
612
+ currentReq = new import_server.NextRequest(currentReq, { headers: merged });
613
+ return currentReq;
614
+ };
615
+ const appliedRequestOverrides = new Headers();
616
+ const queueRequestOverrides = (overrides) => {
617
+ if (!pendingRequestOverrides) pendingRequestOverrides = new Headers();
618
+ mergeRequestHeaderOverrides(pendingRequestOverrides, overrides, requestHeaderPolicy);
619
+ overrides.forEach((v, k) => {
620
+ appliedRequestOverrides.set(k, v);
621
+ });
622
+ };
623
+ try {
624
+ for (let i = 0; i < rules.length; i++) {
625
+ const rule = rules[i];
626
+ const label = ruleLabel(rule, i);
627
+ const activeReq = materializeRequest();
628
+ const reqHost = normalizeHost(
629
+ activeReq.headers.get("host") ?? activeReq.headers.get("x-forwarded-host")
630
+ );
631
+ if (!ruleMatches(activeReq.nextUrl.pathname, reqHost, rule)) continue;
632
+ const startedAt = debug ? performance.now() : 0;
633
+ let raw;
634
+ try {
635
+ raw = await withRuleTimeout(
636
+ Promise.resolve(rule.run(activeReq, ctx, event)),
637
+ ruleTimeoutMs,
638
+ label
639
+ );
640
+ } catch (err) {
641
+ if (config.onError) {
642
+ try {
643
+ const recovered = await config.onError(err, i, activeReq, ctx);
644
+ if (recovered == null) continue;
645
+ if (isNextResult(recovered)) {
646
+ raw = recovered;
647
+ } else if (recovered instanceof Response) {
648
+ headerWrites.push({
649
+ ruleIndex: i,
650
+ ruleName: label,
651
+ items: responseHeaderItems(recovered.headers)
652
+ });
653
+ cookieWrites.push({
654
+ ruleIndex: i,
655
+ ruleName: label,
656
+ items: cookieItemsFromHeaders(recovered.headers)
657
+ });
658
+ if (debug) {
659
+ logger.debug(
660
+ formatPipelineLine({
661
+ method,
662
+ path: pathname,
663
+ name: label,
664
+ traceId,
665
+ outcome: {
666
+ kind: "stop",
667
+ duration: performance.now() - startedAt,
668
+ status: recovered.status
669
+ }
670
+ })
671
+ );
672
+ }
673
+ return finalize(
674
+ recovered,
675
+ headerWrites,
676
+ cookieWrites,
677
+ headerMergeStrategy,
678
+ cookieMergeStrategy,
679
+ logger,
680
+ debug
681
+ );
682
+ } else {
683
+ continue;
684
+ }
685
+ } catch (onErrorErr) {
686
+ logger.error({ rule: label, err: onErrorErr }, "onError handler threw");
687
+ return safeInternalError();
688
+ }
689
+ } else {
690
+ logger.error({ rule: label, err }, "rule threw");
691
+ return safeInternalError();
692
+ }
693
+ }
694
+ const outcome = resolveRuleResult(raw, requestHeaderPolicy, logger, debug);
695
+ const duration = debug ? performance.now() - startedAt : 0;
696
+ if (outcome.kind === "continue") {
697
+ if (outcome.rewriteUrl) {
698
+ pendingRewriteUrl = outcome.rewriteUrl;
699
+ try {
700
+ const parsed = new URL(outcome.rewriteUrl);
701
+ currentReq.nextUrl.pathname = parsed.pathname;
702
+ currentReq.nextUrl.search = parsed.search;
703
+ } catch {
704
+ currentReq.nextUrl.pathname = outcome.rewriteUrl;
705
+ }
706
+ }
707
+ if (outcome.request) queueRequestOverrides(outcome.request);
708
+ if (outcome.responseHeaders.size || outcome.cookies.size) {
709
+ headerWrites.push({ ruleIndex: i, ruleName: label, items: outcome.responseHeaders });
710
+ cookieWrites.push({ ruleIndex: i, ruleName: label, items: outcome.cookies });
711
+ }
712
+ if (debug) {
713
+ const mutated = outcome.responseHeaders.size + outcome.cookies.size + (outcome.request ? 1 : 0) + (outcome.rewriteUrl ? 1 : 0);
714
+ logger.debug(
715
+ formatPipelineLine({
716
+ method,
717
+ path: pathname,
718
+ name: label,
719
+ traceId,
720
+ outcome: mutated === 0 ? { kind: "passed", duration } : { kind: "next", duration, headers: outcome.responseHeaders.size }
721
+ })
722
+ );
723
+ }
724
+ continue;
725
+ }
726
+ headerWrites.push({
727
+ ruleIndex: i,
728
+ ruleName: label,
729
+ items: responseHeaderItems(outcome.response.headers)
730
+ });
731
+ cookieWrites.push({
732
+ ruleIndex: i,
733
+ ruleName: label,
734
+ items: cookieItemsFromHeaders(outcome.response.headers)
735
+ });
736
+ if (debug) {
737
+ logger.debug(
738
+ formatPipelineLine({
739
+ method,
740
+ path: pathname,
741
+ name: label,
742
+ traceId,
743
+ outcome: {
744
+ kind: "stop",
745
+ duration,
746
+ status: outcome.response.status
747
+ }
748
+ })
749
+ );
750
+ }
751
+ const internalFolder = appliedRequestOverrides.get("x-internal-host-folder");
752
+ if (internalFolder && outcome.response.headers.has("x-middleware-rewrite")) {
753
+ const rawRewrite = outcome.response.headers.get("x-middleware-rewrite");
754
+ const cleanFolder = "/" + internalFolder.replace(/^\/+|\/+$/g, "").split("/").filter((seg) => !/^\(.*\)$/.test(seg)).join("/");
755
+ try {
756
+ const parsed = new URL(rawRewrite, currentReq.url);
757
+ if (!parsed.pathname.startsWith(cleanFolder)) {
758
+ parsed.pathname = cleanFolder + (parsed.pathname === "/" ? "" : parsed.pathname);
759
+ outcome.response.headers.set("x-middleware-rewrite", parsed.toString());
760
+ }
761
+ } catch {
762
+ if (!rawRewrite.startsWith(cleanFolder)) {
763
+ outcome.response.headers.set(
764
+ "x-middleware-rewrite",
765
+ cleanFolder + (rawRewrite === "/" ? "" : rawRewrite)
766
+ );
767
+ }
768
+ }
769
+ }
770
+ return finalize(
771
+ outcome.response,
772
+ headerWrites,
773
+ cookieWrites,
774
+ headerMergeStrategy,
775
+ cookieMergeStrategy,
776
+ logger,
777
+ debug
778
+ );
779
+ }
780
+ const finalReq = materializeRequest();
781
+ const internalHostFolder = appliedRequestOverrides.get("x-internal-host-folder");
782
+ let finalRewriteUrl = pendingRewriteUrl;
783
+ if (internalHostFolder) {
784
+ const cleanFolder = "/" + internalHostFolder.replace(/^\/+|\/+$/g, "").split("/").filter((seg) => !/^\(.*\)$/.test(seg)).join("/");
785
+ if (finalRewriteUrl) {
786
+ try {
787
+ const parsed = new URL(finalRewriteUrl, finalReq.url);
788
+ if (!parsed.pathname.startsWith(cleanFolder)) {
789
+ parsed.pathname = cleanFolder + (parsed.pathname === "/" ? "" : parsed.pathname);
790
+ finalRewriteUrl = parsed.toString();
791
+ }
792
+ } catch {
793
+ if (!finalRewriteUrl.startsWith(cleanFolder)) {
794
+ finalRewriteUrl = cleanFolder + (finalRewriteUrl === "/" ? "" : finalRewriteUrl);
795
+ }
796
+ }
797
+ } else {
798
+ const currentPath = finalReq.nextUrl.pathname;
799
+ if (!currentPath.startsWith(cleanFolder)) {
800
+ const targetUrl = finalReq.nextUrl.clone();
801
+ targetUrl.pathname = cleanFolder + (currentPath === "/" ? "" : currentPath);
802
+ finalRewriteUrl = targetUrl.toString();
803
+ }
804
+ }
805
+ }
806
+ let final;
807
+ if (finalRewriteUrl) {
808
+ const rewriteTarget = new URL(finalRewriteUrl, finalReq.url);
809
+ final = import_server.NextResponse.rewrite(rewriteTarget, { request: { headers: finalReq.headers } });
810
+ } else {
811
+ final = import_server.NextResponse.next({ request: { headers: finalReq.headers } });
812
+ }
813
+ return finalize(
814
+ final,
815
+ headerWrites,
816
+ cookieWrites,
817
+ headerMergeStrategy,
818
+ cookieMergeStrategy,
819
+ logger,
820
+ debug
821
+ );
822
+ } catch (err) {
823
+ logger.error({ err }, "proxy chain failed");
824
+ if (config.onError) {
825
+ try {
826
+ const recovered = await config.onError(err, -1, materializeRequest(), ctx);
827
+ if (recovered instanceof Response) return recovered;
828
+ } catch {
829
+ }
830
+ }
831
+ return safeInternalError();
832
+ }
833
+ };
834
+ }
835
+ function finalize(res, headerWrites, cookieWrites, headerStrategy, cookieStrategy, logger, debug) {
836
+ const headers = mergeWrites(headerWrites, headerStrategy, (key, winner, loser) => {
837
+ if (debug) logger.debug({ header: key, winner, loser }, "header conflict resolved");
838
+ });
839
+ const cookies = mergeWrites(cookieWrites, cookieStrategy, (key, winner, loser) => {
840
+ if (debug) logger.debug({ cookie: key, winner, loser }, "cookie conflict resolved");
841
+ });
842
+ applyMergedToResponse(res, headers, cookies);
843
+ return res;
844
+ }
845
+ // Annotate the CommonJS export names for ESM import in node:
846
+ 0 && (module.exports = {
847
+ DEFAULT_BLOCKED_REQUEST_HEADERS,
848
+ NEXT,
849
+ RuleTimeoutError,
850
+ applyMergedToResponse,
851
+ assertIncludeExcludeNoOverlap,
852
+ assertOverrideMechanismSupported,
853
+ compileScopes,
854
+ cookieItemsFromHeaders,
855
+ createConsoleLogger,
856
+ createProxyContext,
857
+ decodeOverriddenRequestHeaders,
858
+ defineProxy,
859
+ formatPipelineLine,
860
+ headersDiffer,
861
+ host,
862
+ isNextResult,
863
+ isRequestHeaderAllowed,
864
+ listSetCookie,
865
+ mergeRequestHeaderOverrides,
866
+ mergeWrites,
867
+ next,
868
+ normalizeHost,
869
+ parseCookieName,
870
+ path,
871
+ pathMatches,
872
+ proxyChain,
873
+ responseHeaderItems,
874
+ ruleLabel,
875
+ ruleMatches,
876
+ safeInternalError,
877
+ toPathRegex,
878
+ toRule,
879
+ toStatelessRegExp,
880
+ withPaths,
881
+ withRuleTimeout
882
+ });