vinext 1.0.0-beta.1 → 1.0.0-beta.2
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/README.md +4 -1
- package/dist/build/client-build-config.js +1 -0
- package/dist/build/report.d.ts +8 -1
- package/dist/build/report.js +12 -2
- package/dist/check.js +15 -2
- package/dist/cli.js +3 -2
- package/dist/config/config-matchers.d.ts +13 -37
- package/dist/config/config-matchers.js +31 -121
- package/dist/config/next-config.d.ts +3 -2
- package/dist/config/next-config.js +3 -1
- package/dist/config/request-context.d.ts +14 -0
- package/dist/config/request-context.js +26 -0
- package/dist/entries/app-rsc-entry.js +27 -9
- package/dist/entries/app-rsc-manifest.js +17 -0
- package/dist/entries/pages-server-entry.js +3 -2
- package/dist/index.js +82 -58
- package/dist/init.d.ts +6 -4
- package/dist/init.js +20 -22
- package/dist/plugins/og-assets.js +1 -1
- package/dist/routing/app-route-graph.d.ts +13 -5
- package/dist/routing/app-route-graph.js +59 -13
- package/dist/routing/app-router.js +12 -6
- package/dist/routing/file-matcher.d.ts +11 -22
- package/dist/routing/file-matcher.js +21 -33
- package/dist/routing/pages-router.js +23 -29
- package/dist/routing/route-matching.d.ts +14 -1
- package/dist/routing/route-matching.js +13 -1
- package/dist/routing/route-pattern.d.ts +2 -1
- package/dist/routing/route-pattern.js +7 -4
- package/dist/routing/route-trie.d.ts +5 -1
- package/dist/routing/route-trie.js +5 -2
- package/dist/server/app-browser-entry.js +7 -6
- package/dist/server/app-browser-history-controller.d.ts +1 -1
- package/dist/server/app-browser-history-controller.js +3 -3
- package/dist/server/app-browser-navigation-controller.js +1 -1
- package/dist/server/app-browser-state.js +1 -1
- package/dist/server/app-browser-visible-commit.js +1 -1
- package/dist/server/app-fallback-renderer.d.ts +3 -0
- package/dist/server/app-fallback-renderer.js +4 -2
- package/dist/server/app-middleware.js +2 -1
- package/dist/server/app-page-boundary-render.d.ts +12 -1
- package/dist/server/app-page-boundary-render.js +84 -2
- package/dist/server/app-page-cache-finalizer.d.ts +1 -1
- package/dist/server/app-page-cache.d.ts +1 -1
- package/dist/server/app-page-dispatch.d.ts +11 -2
- package/dist/server/app-page-dispatch.js +14 -17
- package/dist/server/app-page-element-builder.d.ts +7 -2
- package/dist/server/app-page-element-builder.js +112 -27
- package/dist/server/app-page-head.d.ts +54 -2
- package/dist/server/app-page-head.js +164 -78
- package/dist/server/app-page-http-access-fallback-metadata.d.ts +29 -0
- package/dist/server/app-page-http-access-fallback-metadata.js +106 -0
- package/dist/server/app-page-render.d.ts +2 -2
- package/dist/server/app-page-render.js +1 -1
- package/dist/server/app-page-request.d.ts +12 -0
- package/dist/server/app-page-request.js +133 -13
- package/dist/server/app-page-route-wiring.d.ts +9 -0
- package/dist/server/app-page-route-wiring.js +43 -3
- package/dist/server/app-post-middleware-context.d.ts +1 -1
- package/dist/server/app-post-middleware-context.js +1 -1
- package/dist/server/app-route-handler-dispatch.js +31 -17
- package/dist/server/app-route-handler-execution.d.ts +6 -1
- package/dist/server/app-route-handler-execution.js +42 -16
- package/dist/server/app-route-handler-policy.d.ts +2 -0
- package/dist/server/app-route-handler-policy.js +3 -3
- package/dist/server/app-route-module-loader.d.ts +3 -1
- package/dist/server/app-route-module-loader.js +1 -0
- package/dist/server/app-rsc-cache-busting.d.ts +1 -1
- package/dist/server/app-rsc-errors.d.ts +1 -1
- package/dist/server/app-rsc-errors.js +12 -2
- package/dist/server/app-rsc-handler.d.ts +10 -5
- package/dist/server/app-rsc-handler.js +52 -24
- package/dist/server/app-rsc-request-normalization.d.ts +3 -2
- package/dist/server/app-rsc-request-normalization.js +8 -2
- package/dist/server/app-rsc-response-finalizer.d.ts +2 -2
- package/dist/server/app-rsc-response-finalizer.js +5 -11
- package/dist/server/app-rsc-route-matching.d.ts +20 -0
- package/dist/server/app-rsc-route-matching.js +87 -11
- package/dist/server/app-segment-config.js +13 -3
- package/dist/server/app-server-action-execution.d.ts +3 -1
- package/dist/server/app-server-action-execution.js +9 -7
- package/dist/server/app-ssr-entry.js +2 -2
- package/dist/server/app-ssr-stream.d.ts +1 -7
- package/dist/server/app-ssr-stream.js +2 -10
- package/dist/server/config-headers.d.ts +24 -0
- package/dist/server/config-headers.js +52 -0
- package/dist/server/dev-initial-server-error.d.ts +1 -1
- package/dist/server/dev-server.js +29 -13
- package/dist/server/isr-cache.d.ts +1 -1
- package/dist/server/middleware-matcher-pattern.d.ts +22 -0
- package/dist/server/middleware-matcher-pattern.js +219 -0
- package/dist/server/middleware-matcher.d.ts +2 -7
- package/dist/server/middleware-matcher.js +13 -60
- package/dist/server/middleware-path-to-regexp.d.ts +14 -0
- package/dist/server/middleware-path-to-regexp.js +228 -0
- package/dist/server/middleware-runtime.js +4 -3
- package/dist/server/middleware.d.ts +1 -1
- package/dist/server/middleware.js +2 -1
- package/dist/server/pages-data-route.d.ts +11 -1
- package/dist/server/pages-data-route.js +16 -1
- package/dist/server/pages-get-initial-props.d.ts +15 -2
- package/dist/server/pages-get-initial-props.js +16 -6
- package/dist/server/pages-page-data.d.ts +3 -1
- package/dist/server/pages-page-data.js +16 -7
- package/dist/server/pages-page-handler.d.ts +4 -2
- package/dist/server/pages-page-handler.js +16 -17
- package/dist/server/pages-page-response.js +1 -1
- package/dist/server/pages-readiness.d.ts +3 -3
- package/dist/server/pages-request-pipeline.d.ts +1 -0
- package/dist/server/pages-request-pipeline.js +25 -11
- package/dist/server/pages-router-entry.js +6 -0
- package/dist/server/prerender-route-params.js +2 -2
- package/dist/server/prod-server.d.ts +1 -4
- package/dist/server/prod-server.js +36 -23
- package/dist/server/request-pipeline.d.ts +15 -29
- package/dist/server/request-pipeline.js +24 -59
- package/dist/server/rsc-stream-hints.js +122 -18
- package/dist/shims/app.d.ts +1 -1
- package/dist/shims/cache-request-state.d.ts +27 -1
- package/dist/shims/cache-request-state.js +60 -1
- package/dist/shims/cache-runtime.d.ts +12 -1
- package/dist/shims/cache-runtime.js +22 -7
- package/dist/shims/cache.d.ts +5 -5
- package/dist/shims/cache.js +26 -10
- package/dist/shims/document.d.ts +42 -83
- package/dist/shims/document.js +62 -53
- package/dist/shims/dynamic.d.ts +5 -25
- package/dist/shims/dynamic.js +29 -8
- package/dist/shims/error.d.ts +1 -1
- package/dist/shims/error.js +1 -1
- package/dist/shims/fetch-cache.js +12 -3
- package/dist/shims/font-google-base.d.ts +11 -4
- package/dist/shims/font-local.d.ts +10 -3
- package/dist/shims/head.d.ts +13 -2
- package/dist/shims/head.js +3 -3
- package/dist/shims/headers.d.ts +28 -31
- package/dist/shims/headers.js +109 -49
- package/dist/shims/image.d.ts +27 -37
- package/dist/shims/image.js +21 -14
- package/dist/shims/internal/cookie-serialize.d.ts +3 -1
- package/dist/shims/internal/cookie-serialize.js +3 -1
- package/dist/shims/internal/hybrid-client-route-owner.js +3 -1
- package/dist/shims/internal/utils.d.ts +3 -3
- package/dist/shims/legacy-image.d.ts +3 -37
- package/dist/shims/legacy-image.js +6 -16
- package/dist/shims/link.d.ts +15 -22
- package/dist/shims/link.js +20 -11
- package/dist/shims/navigation-context-state.d.ts +1 -1
- package/dist/shims/navigation.d.ts +7 -3
- package/dist/shims/navigation.js +24 -13
- package/dist/shims/navigation.react-server.js +1 -1
- package/dist/shims/next-shims-augmentations.d.ts +1 -0
- package/dist/shims/next-shims-public.d.ts +1 -0
- package/dist/shims/public-shim-map.json.js +103 -0
- package/dist/shims/readonly-url-search-params.d.ts +3 -3
- package/dist/shims/readonly-url-search-params.js +3 -3
- package/dist/shims/request-state-types.d.ts +1 -1
- package/dist/shims/router.d.ts +55 -21
- package/dist/shims/router.js +162 -37
- package/dist/shims/script.d.ts +6 -10
- package/dist/shims/script.js +6 -3
- package/dist/shims/server.d.ts +45 -22
- package/dist/shims/server.js +44 -5
- package/dist/shims/unified-request-context.d.ts +1 -1
- package/dist/shims/unified-request-context.js +2 -0
- package/dist/shims/url-safety.d.ts +1 -1
- package/dist/shims/url-safety.js +2 -2
- package/dist/typegen.d.ts +8 -2
- package/dist/typegen.js +76 -15
- package/dist/utils/domain-locale.d.ts +2 -2
- package/dist/utils/external-url.d.ts +5 -0
- package/dist/utils/external-url.js +7 -0
- package/dist/utils/regex-safety.d.ts +8 -0
- package/dist/utils/regex-safety.js +737 -0
- package/package.json +13 -2
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
import { addBasePathToPathname, hasBasePath } from "../utils/base-path.js";
|
|
2
2
|
import { patternToNextFormat } from "../routing/route-validation.js";
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
3
|
+
import { requestContextFromRequest } from "../config/request-context.js";
|
|
4
|
+
import { isExternalUrl } from "../utils/external-url.js";
|
|
5
|
+
import { applyMiddlewareRequestHeaders, matchRedirect, matchRewrite, preserveRedirectDestinationQuery, proxyExternalRequest, sanitizeDestination } from "../config/config-matchers.js";
|
|
6
|
+
import { cloneRequestWithUrl, normalizeTrailingSlash } from "./request-pipeline.js";
|
|
5
7
|
import { mergeRewriteQuery } from "../utils/query.js";
|
|
6
8
|
import { normalizeDefaultLocalePathname, stripI18nLocaleForApiRoute } from "./pages-i18n.js";
|
|
7
9
|
import { buildMiddlewarePrefetchSkipResponse } from "./pages-data-route.js";
|
|
10
|
+
import { applyConfigHeadersToHeaderRecord } from "./config-headers.js";
|
|
8
11
|
import { mergeHeaders } from "./worker-utils.js";
|
|
9
12
|
//#region src/server/pages-request-pipeline.ts
|
|
10
13
|
async function fetchWorkerFilesystemRoute(request, requestPathname, phase, fetchAsset) {
|
|
@@ -53,6 +56,7 @@ async function runPagesRequest(request, deps) {
|
|
|
53
56
|
const url = new URL(request.url);
|
|
54
57
|
let pathname = url.pathname;
|
|
55
58
|
const search = url.search;
|
|
59
|
+
const requestConfigPathname = deps.configMatchPathname ?? pathname;
|
|
56
60
|
const basePathState = {
|
|
57
61
|
basePath,
|
|
58
62
|
hadBasePath
|
|
@@ -66,9 +70,9 @@ async function runPagesRequest(request, deps) {
|
|
|
66
70
|
}
|
|
67
71
|
const reqCtx = requestContextFromRequest(request);
|
|
68
72
|
const requestHostname = i18nConfig ? url.hostname : "";
|
|
69
|
-
const
|
|
73
|
+
const requestConfigMatchPathname = i18nConfig ? normalizeDefaultLocalePathname(requestConfigPathname, i18nConfig, { hostname: requestHostname }) : requestConfigPathname;
|
|
70
74
|
if (configRedirects.length) {
|
|
71
|
-
const redirect = matchRedirect(
|
|
75
|
+
const redirect = matchRedirect(requestConfigMatchPathname, configRedirects, reqCtx, basePathState);
|
|
72
76
|
if (redirect) {
|
|
73
77
|
const location = preserveRedirectDestinationQuery(sanitizeDestination(basePath && hadBasePath && !isExternalUrl(redirect.destination) && !hasBasePath(redirect.destination, basePath) ? basePath + redirect.destination : redirect.destination), deps.rawSearch ?? search);
|
|
74
78
|
return {
|
|
@@ -82,6 +86,7 @@ async function runPagesRequest(request, deps) {
|
|
|
82
86
|
}
|
|
83
87
|
const originalResolvedUrl = pathname + search;
|
|
84
88
|
let resolvedUrl = originalResolvedUrl;
|
|
89
|
+
let resolvedPathnameIsRequestPathname = true;
|
|
85
90
|
const middlewareHeaders = {};
|
|
86
91
|
let middlewareStatus;
|
|
87
92
|
const serveFilesystemRoute = async (requestPathname, phase) => {
|
|
@@ -131,7 +136,10 @@ async function runPagesRequest(request, deps) {
|
|
|
131
136
|
else if (existing) middlewareHeaders[key] = [existing, value];
|
|
132
137
|
else middlewareHeaders[key] = [value];
|
|
133
138
|
} else middlewareHeaders[key] = value;
|
|
134
|
-
if (result.rewriteUrl)
|
|
139
|
+
if (result.rewriteUrl) {
|
|
140
|
+
resolvedUrl = result.rewriteUrl;
|
|
141
|
+
resolvedPathnameIsRequestPathname = false;
|
|
142
|
+
}
|
|
135
143
|
middlewareStatus = result.status ?? result.rewriteStatus;
|
|
136
144
|
}
|
|
137
145
|
const { postMwReqCtx, request: postMwReq } = applyMiddlewareRequestHeaders(middlewareHeaders, request, { preserveCredentialHeaders: isExternalUrl(resolvedUrl) });
|
|
@@ -143,6 +151,7 @@ async function runPagesRequest(request, deps) {
|
|
|
143
151
|
});
|
|
144
152
|
let resolvedPathname = pathnameForResolvedUrl(resolvedUrl);
|
|
145
153
|
const matchResolvedPathname = (p) => i18nConfig ? normalizeDefaultLocalePathname(p, i18nConfig, { hostname: requestHostname }) : p;
|
|
154
|
+
const configSourcePathname = () => resolvedPathnameIsRequestPathname ? requestConfigMatchPathname : matchResolvedPathname(resolvedPathname);
|
|
146
155
|
const matchedPathnameForRoute = (routePattern) => {
|
|
147
156
|
const matchedPathname = routePattern ? patternToNextFormat(routePattern) : resolvedPathname;
|
|
148
157
|
if (!i18nConfig) return matchedPathname;
|
|
@@ -161,7 +170,7 @@ async function runPagesRequest(request, deps) {
|
|
|
161
170
|
};
|
|
162
171
|
if (configHeaders.length) applyConfigHeadersToHeaderRecord(middlewareHeaders, {
|
|
163
172
|
configHeaders,
|
|
164
|
-
pathname:
|
|
173
|
+
pathname: requestConfigMatchPathname,
|
|
165
174
|
requestContext: reqCtx,
|
|
166
175
|
basePathState
|
|
167
176
|
});
|
|
@@ -173,7 +182,7 @@ async function runPagesRequest(request, deps) {
|
|
|
173
182
|
if (directFilesystemResult) return directFilesystemResult;
|
|
174
183
|
let configRewriteFired = false;
|
|
175
184
|
for (const rewrite of configRewrites.beforeFiles ?? []) {
|
|
176
|
-
const rewritten = matchRewrite(
|
|
185
|
+
const rewritten = matchRewrite(configSourcePathname(), [rewrite], rewriteRequestContext(), basePathState);
|
|
177
186
|
if (rewritten) {
|
|
178
187
|
if (isExternalUrl(rewritten)) return {
|
|
179
188
|
type: "response",
|
|
@@ -181,6 +190,7 @@ async function runPagesRequest(request, deps) {
|
|
|
181
190
|
};
|
|
182
191
|
resolvedUrl = mergeRewriteQuery(resolvedUrl, rewritten);
|
|
183
192
|
resolvedPathname = pathnameForResolvedUrl(resolvedUrl);
|
|
193
|
+
resolvedPathnameIsRequestPathname = false;
|
|
184
194
|
configRewriteFired = true;
|
|
185
195
|
}
|
|
186
196
|
}
|
|
@@ -227,7 +237,7 @@ async function runPagesRequest(request, deps) {
|
|
|
227
237
|
let pageMatch = !isOutsideBasePathUnclaimed() && deps.matchPageRoute ? deps.matchPageRoute(resolvedPathname, request) : null;
|
|
228
238
|
let resolvedPathnameChanged = false;
|
|
229
239
|
if (!pageMatch || pageMatch.route.isDynamic) for (const rewrite of configRewrites.afterFiles ?? []) {
|
|
230
|
-
const rewritten = matchRewrite(
|
|
240
|
+
const rewritten = matchRewrite(configSourcePathname(), [rewrite], rewriteRequestContext(), basePathState);
|
|
231
241
|
if (rewritten) {
|
|
232
242
|
if (isExternalUrl(rewritten)) return {
|
|
233
243
|
type: "response",
|
|
@@ -235,6 +245,7 @@ async function runPagesRequest(request, deps) {
|
|
|
235
245
|
};
|
|
236
246
|
resolvedUrl = mergeRewriteQuery(resolvedUrl, rewritten);
|
|
237
247
|
resolvedPathname = pathnameForResolvedUrl(resolvedUrl);
|
|
248
|
+
resolvedPathnameIsRequestPathname = false;
|
|
238
249
|
configRewriteFired = true;
|
|
239
250
|
resolvedPathnameChanged = true;
|
|
240
251
|
const afterFilesFilesystemResult = await serveFilesystemRoute(resolvedPathname, "afterFiles");
|
|
@@ -253,7 +264,7 @@ async function runPagesRequest(request, deps) {
|
|
|
253
264
|
if (typeof deps.renderPage === "function") {
|
|
254
265
|
let renderPageMatch = pageMatch;
|
|
255
266
|
if ((isOutsideBasePathUnclaimed() || isDataReq || isDataRequest) && !renderPageMatch && configRewrites.fallback?.length) for (const rewrite of configRewrites.fallback) {
|
|
256
|
-
const fallbackRewrite = matchRewrite(
|
|
267
|
+
const fallbackRewrite = matchRewrite(configSourcePathname(), [rewrite], rewriteRequestContext(), basePathState);
|
|
257
268
|
if (!fallbackRewrite) continue;
|
|
258
269
|
if (isExternalUrl(fallbackRewrite)) return {
|
|
259
270
|
type: "response",
|
|
@@ -261,6 +272,7 @@ async function runPagesRequest(request, deps) {
|
|
|
261
272
|
};
|
|
262
273
|
resolvedUrl = mergeRewriteQuery(resolvedUrl, fallbackRewrite);
|
|
263
274
|
resolvedPathname = pathnameForResolvedUrl(resolvedUrl);
|
|
275
|
+
resolvedPathnameIsRequestPathname = false;
|
|
264
276
|
configRewriteFired = true;
|
|
265
277
|
const fallbackFilesystemResult = await serveFilesystemRoute(resolvedPathname, "fallback");
|
|
266
278
|
if (fallbackFilesystemResult) return fallbackFilesystemResult;
|
|
@@ -281,7 +293,7 @@ async function runPagesRequest(request, deps) {
|
|
|
281
293
|
let response = await deps.renderPage(request, resolvedUrl, initialRenderOptions, stagedHeaders);
|
|
282
294
|
let matchedFallbackRewrite = false;
|
|
283
295
|
if (response.status === 404 && shouldDeferErrorPageOnMiss && configRewrites.fallback?.length) for (const rewrite of configRewrites.fallback) {
|
|
284
|
-
const fallbackRewrite = matchRewrite(
|
|
296
|
+
const fallbackRewrite = matchRewrite(configSourcePathname(), [rewrite], rewriteRequestContext(), basePathState);
|
|
285
297
|
if (!fallbackRewrite) continue;
|
|
286
298
|
if (isExternalUrl(fallbackRewrite)) return {
|
|
287
299
|
type: "response",
|
|
@@ -289,6 +301,7 @@ async function runPagesRequest(request, deps) {
|
|
|
289
301
|
};
|
|
290
302
|
resolvedUrl = mergeRewriteQuery(resolvedUrl, fallbackRewrite);
|
|
291
303
|
resolvedPathname = pathnameForResolvedUrl(resolvedUrl);
|
|
304
|
+
resolvedPathnameIsRequestPathname = false;
|
|
292
305
|
configRewriteFired = true;
|
|
293
306
|
const fallbackFilesystemResult = await serveFilesystemRoute(resolvedPathname, "fallback");
|
|
294
307
|
if (fallbackFilesystemResult) return fallbackFilesystemResult;
|
|
@@ -325,7 +338,7 @@ async function runPagesRequest(request, deps) {
|
|
|
325
338
|
}
|
|
326
339
|
let devPageMatch = isOutsideBasePathUnclaimed() ? null : resolvedPathnameChanged ? deps.matchPageRoute ? deps.matchPageRoute(resolvedPathname, request) : null : pageMatch;
|
|
327
340
|
if (!devPageMatch && configRewrites.fallback?.length) for (const rewrite of configRewrites.fallback) {
|
|
328
|
-
const fallbackRewrite = matchRewrite(
|
|
341
|
+
const fallbackRewrite = matchRewrite(configSourcePathname(), [rewrite], rewriteRequestContext(), basePathState);
|
|
329
342
|
if (!fallbackRewrite) continue;
|
|
330
343
|
if (isExternalUrl(fallbackRewrite)) return {
|
|
331
344
|
type: "response",
|
|
@@ -333,6 +346,7 @@ async function runPagesRequest(request, deps) {
|
|
|
333
346
|
};
|
|
334
347
|
resolvedUrl = mergeRewriteQuery(resolvedUrl, fallbackRewrite);
|
|
335
348
|
resolvedPathname = pathnameForResolvedUrl(resolvedUrl);
|
|
349
|
+
resolvedPathnameIsRequestPathname = false;
|
|
336
350
|
configRewriteFired = true;
|
|
337
351
|
const fallbackFilesystemResult = await serveFilesystemRoute(resolvedPathname, "fallback");
|
|
338
352
|
if (fallbackFilesystemResult) return fallbackFilesystemResult;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { normalizePathnameForRouteMatchStrict } from "../routing/utils.js";
|
|
1
2
|
import { hasBasePath, stripBasePath } from "../utils/base-path.js";
|
|
2
3
|
import { notFoundStaticAssetResponse } from "./http-error-responses.js";
|
|
3
4
|
import { isOpenRedirectShaped } from "./open-redirect.js";
|
|
@@ -52,6 +53,11 @@ async function handleRequest(request, env, ctx) {
|
|
|
52
53
|
try {
|
|
53
54
|
let pathname = new URL(request.url).pathname;
|
|
54
55
|
if (isOpenRedirectShaped(pathname)) return new Response("This page could not be found", { status: 404 });
|
|
56
|
+
try {
|
|
57
|
+
normalizePathnameForRouteMatchStrict(pathname);
|
|
58
|
+
} catch {
|
|
59
|
+
return new Response("Bad Request", { status: 400 });
|
|
60
|
+
}
|
|
55
61
|
const missingBuildAsset = isNextStaticPath(pathname, basePath, assetPathPrefix);
|
|
56
62
|
request = cloneRequestWithHeaders(request, filterInternalHeaders(request.headers));
|
|
57
63
|
const hadBasePath = !basePath || hasBasePath(pathname, basePath);
|
|
@@ -58,11 +58,11 @@ function decodedPrerenderRouteParamEquals(prerenderValue, matchedValue) {
|
|
|
58
58
|
if (prerenderValue.length !== matchedValue.length) return false;
|
|
59
59
|
return prerenderValue.every((item, index) => {
|
|
60
60
|
const decoded = decodePrerenderRouteParam(item);
|
|
61
|
-
return decoded !== null && decoded === matchedValue[index];
|
|
61
|
+
return item === matchedValue[index] || decoded !== null && decoded === matchedValue[index];
|
|
62
62
|
});
|
|
63
63
|
}
|
|
64
64
|
const decoded = decodePrerenderRouteParam(prerenderValue);
|
|
65
|
-
return decoded !== null && decoded === matchedValue;
|
|
65
|
+
return prerenderValue === matchedValue || decoded !== null && decoded === matchedValue;
|
|
66
66
|
}
|
|
67
67
|
function prerenderRouteParamsPayloadMatchesRoute(payload, routePattern, params) {
|
|
68
68
|
return matchPrerenderRouteParamsPayload(payload, routePattern, params)?.kind === "exact";
|
|
@@ -101,10 +101,7 @@ declare function tryServeStatic(req: IncomingMessage, res: ServerResponse, clien
|
|
|
101
101
|
* Convert a Node.js IncomingMessage to a Web Request object.
|
|
102
102
|
*
|
|
103
103
|
* When `urlOverride` is provided, it is used as the path + query string
|
|
104
|
-
* instead of `req.url`.
|
|
105
|
-
* caller has already decoded and normalized the pathname (e.g. the App
|
|
106
|
-
* Router prod server normalizes before static-asset lookup, and can pass
|
|
107
|
-
* the result here so the downstream RSC handler doesn't re-normalize).
|
|
104
|
+
* instead of `req.url`.
|
|
108
105
|
*/
|
|
109
106
|
declare function nodeToWebRequest(req: IncomingMessage, urlOverride?: string, prerenderSecret?: string): Request;
|
|
110
107
|
/**
|
|
@@ -6,9 +6,9 @@ import { VINEXT_STATIC_FILE_HEADER } from "./headers.js";
|
|
|
6
6
|
import { normalizePath } from "./normalize-path.js";
|
|
7
7
|
import { notFoundResponse } from "./http-error-responses.js";
|
|
8
8
|
import { isOpenRedirectShaped } from "./open-redirect.js";
|
|
9
|
-
import { filterInternalHeaders } from "./request-pipeline.js";
|
|
9
|
+
import { canonicalizeRequestPathname, filterInternalHeaders } from "./request-pipeline.js";
|
|
10
10
|
import { isUnknownRecord } from "../utils/record.js";
|
|
11
|
-
import { buildNextDataNotFoundResponse, isNextDataPathname, normalizeNextDataPagePathname, parseNextDataPathname } from "./pages-data-route.js";
|
|
11
|
+
import { buildNextDataNotFoundResponse, encodeUrlParserIgnoredCharacters, isNextDataPathname, normalizeNextDataPagePathname, parseNextDataPathname, urlParserCreatesPagesDataPath } from "./pages-data-route.js";
|
|
12
12
|
import { ASSET_PREFIX_URL_DIR, assetPrefixPathname, isAbsoluteAssetPrefix } from "../utils/asset-prefix.js";
|
|
13
13
|
import { setPagesClientAssets } from "./pages-client-assets.js";
|
|
14
14
|
import { resolveRequestHost, resolveRequestProtocol, trustProxy, trustedHosts } from "./proxy-trust.js";
|
|
@@ -555,10 +555,7 @@ async function statIfFile(filePath) {
|
|
|
555
555
|
* Convert a Node.js IncomingMessage to a Web Request object.
|
|
556
556
|
*
|
|
557
557
|
* When `urlOverride` is provided, it is used as the path + query string
|
|
558
|
-
* instead of `req.url`.
|
|
559
|
-
* caller has already decoded and normalized the pathname (e.g. the App
|
|
560
|
-
* Router prod server normalizes before static-asset lookup, and can pass
|
|
561
|
-
* the result here so the downstream RSC handler doesn't re-normalize).
|
|
558
|
+
* instead of `req.url`.
|
|
562
559
|
*/
|
|
563
560
|
function nodeToWebRequest(req, urlOverride, prerenderSecret) {
|
|
564
561
|
const origin = `${resolveRequestProtocol(req)}://${resolveRequestHost(req, "localhost")}`;
|
|
@@ -873,9 +870,14 @@ async function startAppRouterServer(options) {
|
|
|
873
870
|
return;
|
|
874
871
|
}
|
|
875
872
|
try {
|
|
876
|
-
const
|
|
877
|
-
const request = nodeToWebRequest(req, pathname + qs, prerenderSecret);
|
|
873
|
+
const request = nodeToWebRequest(req, rawUrl, prerenderSecret);
|
|
878
874
|
const response = await rscHandler(request);
|
|
875
|
+
if (missingBuildAsset && response.status === 404) {
|
|
876
|
+
cancelResponseBody(response);
|
|
877
|
+
res.writeHead(404, { "Content-Type": "text/plain; charset=utf-8" });
|
|
878
|
+
res.end("Not Found");
|
|
879
|
+
return;
|
|
880
|
+
}
|
|
879
881
|
const staticFileSignal = response.headers.get(VINEXT_STATIC_FILE_HEADER);
|
|
880
882
|
if (staticFileSignal) {
|
|
881
883
|
let staticFilePath = "/";
|
|
@@ -891,12 +893,6 @@ async function startAppRouterServer(options) {
|
|
|
891
893
|
await sendWebResponse(notFoundResponse({ headers: toWebHeaders(staticResponseHeaders) }), req, res, compress);
|
|
892
894
|
return;
|
|
893
895
|
}
|
|
894
|
-
if (missingBuildAsset && response.status === 404) {
|
|
895
|
-
cancelResponseBody(response);
|
|
896
|
-
res.writeHead(404, { "Content-Type": "text/plain; charset=utf-8" });
|
|
897
|
-
res.end("Not Found");
|
|
898
|
-
return;
|
|
899
|
-
}
|
|
900
896
|
await sendWebResponse(response, req, res, compress);
|
|
901
897
|
} catch (e) {
|
|
902
898
|
console.error("[vinext] Server error:", e);
|
|
@@ -987,8 +983,9 @@ async function startPagesRouterServer(options) {
|
|
|
987
983
|
res.end("This page could not be found");
|
|
988
984
|
return;
|
|
989
985
|
}
|
|
990
|
-
const rawPagesPathname = rawPagesPathnameBeforeNormalize.replaceAll("\\", "/");
|
|
986
|
+
const rawPagesPathname = canonicalizeRequestPathname(rawPagesPathnameBeforeNormalize.replaceAll("\\", "/"));
|
|
991
987
|
const rawQs = rawUrl.includes("?") ? rawUrl.slice(rawUrl.indexOf("?")) : "";
|
|
988
|
+
let requestPathname = normalizePath(rawPagesPathname);
|
|
992
989
|
let pathname;
|
|
993
990
|
try {
|
|
994
991
|
pathname = normalizePath(normalizePathnameForRouteMatchStrict(rawPagesPathname));
|
|
@@ -997,7 +994,7 @@ async function startPagesRouterServer(options) {
|
|
|
997
994
|
res.end("Bad Request");
|
|
998
995
|
return;
|
|
999
996
|
}
|
|
1000
|
-
let url =
|
|
997
|
+
let url = requestPathname + rawQs;
|
|
1001
998
|
if (pathname === "/__vinext/prerender/pages-static-paths") {
|
|
1002
999
|
const secret = req.headers[VINEXT_PRERENDER_SECRET_HEADER];
|
|
1003
1000
|
if (!prerenderSecret || secret !== prerenderSecret) {
|
|
@@ -1058,18 +1055,31 @@ async function startPagesRouterServer(options) {
|
|
|
1058
1055
|
return;
|
|
1059
1056
|
}
|
|
1060
1057
|
try {
|
|
1061
|
-
const hadBasePath = !basePath || hasBasePath(
|
|
1058
|
+
const hadBasePath = !basePath || hasBasePath(requestPathname, basePath);
|
|
1059
|
+
let configMatchPathname = stripBasePath(requestPathname, basePath);
|
|
1062
1060
|
{
|
|
1063
|
-
const
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1061
|
+
const strippedPathname = stripBasePath(pathname, basePath);
|
|
1062
|
+
const strippedRequestPathname = stripBasePath(requestPathname, basePath);
|
|
1063
|
+
pathname = strippedPathname;
|
|
1064
|
+
if (strippedRequestPathname !== requestPathname) {
|
|
1065
|
+
requestPathname = strippedRequestPathname;
|
|
1066
|
+
url = requestPathname + rawQs;
|
|
1067
1067
|
}
|
|
1068
1068
|
}
|
|
1069
|
+
if (urlParserCreatesPagesDataPath(pathname)) {
|
|
1070
|
+
res.writeHead(404);
|
|
1071
|
+
res.end("This page could not be found");
|
|
1072
|
+
return;
|
|
1073
|
+
}
|
|
1074
|
+
requestPathname = encodeUrlParserIgnoredCharacters(requestPathname);
|
|
1075
|
+
{
|
|
1076
|
+
const qs = url.includes("?") ? url.slice(url.indexOf("?")) : "";
|
|
1077
|
+
url = requestPathname + qs;
|
|
1078
|
+
}
|
|
1069
1079
|
let isDataReq = false;
|
|
1070
1080
|
const originalRenderUrl = url;
|
|
1071
|
-
if (isNextDataPathname(
|
|
1072
|
-
const dataMatch = pagesBuildId ? parseNextDataPathname(
|
|
1081
|
+
if (isNextDataPathname(requestPathname)) {
|
|
1082
|
+
const dataMatch = pagesBuildId ? parseNextDataPathname(requestPathname, pagesBuildId) : null;
|
|
1073
1083
|
if (!dataMatch) {
|
|
1074
1084
|
await sendWebResponse(buildNextDataNotFoundResponse(), req, res, compress);
|
|
1075
1085
|
return;
|
|
@@ -1078,7 +1088,9 @@ async function startPagesRouterServer(options) {
|
|
|
1078
1088
|
const qs = url.includes("?") ? url.slice(url.indexOf("?")) : "";
|
|
1079
1089
|
const pagePathname = normalizeNextDataPagePathname(dataMatch.pagePathname, hasMiddleware && trailingSlash);
|
|
1080
1090
|
url = pagePathname + qs;
|
|
1091
|
+
requestPathname = pagePathname;
|
|
1081
1092
|
pathname = pagePathname;
|
|
1093
|
+
configMatchPathname = pagePathname;
|
|
1082
1094
|
}
|
|
1083
1095
|
const protocol = resolveRequestProtocol(req);
|
|
1084
1096
|
const hostHeader = resolveRequestHost(req, `${host}:${port}`);
|
|
@@ -1105,6 +1117,7 @@ async function startPagesRouterServer(options) {
|
|
|
1105
1117
|
hasMiddleware,
|
|
1106
1118
|
ctx: void 0,
|
|
1107
1119
|
rawSearch: rawQs,
|
|
1120
|
+
configMatchPathname,
|
|
1108
1121
|
matchPageRoute: matchPageRoute ?? null,
|
|
1109
1122
|
runMiddleware: typeof runMiddleware === "function" ? wrapMiddlewareWithBasePath(runMiddleware, basePath, hadBasePath) : null,
|
|
1110
1123
|
renderPage: typeof renderPage === "function" ? (request, resolvedUrl, options, stagedHeaders) => renderPage(request, resolvedUrl, ssrManifest, void 0, stagedHeaders, {
|
|
@@ -1,10 +1,21 @@
|
|
|
1
|
-
import { NextHeader } from "../config/next-config.js";
|
|
2
|
-
import { BasePathMatchState, RequestContext } from "../config/config-matchers.js";
|
|
3
1
|
import { INTERNAL_HEADERS, VINEXT_INTERNAL_HEADERS } from "./headers.js";
|
|
4
|
-
import { isOpenRedirectShaped } from "./open-redirect.js";
|
|
5
2
|
import { hasBasePath, stripBasePath } from "../utils/base-path.js";
|
|
3
|
+
import { isOpenRedirectShaped } from "./open-redirect.js";
|
|
6
4
|
|
|
7
5
|
//#region src/server/request-pipeline.d.ts
|
|
6
|
+
/**
|
|
7
|
+
* Apply the URL Standard's pathname canonicalization without decoding and
|
|
8
|
+
* re-encoding ordinary percent escapes.
|
|
9
|
+
*
|
|
10
|
+
* In particular, WHATWG URLs remove literal and percent-encoded dot segments
|
|
11
|
+
* (`/%2e/about` becomes `/about`) while preserving unrelated spellings such
|
|
12
|
+
* as `/%61bout`, `%2F`, `%5C`, and `%252F` byte-for-byte. Node request adapters
|
|
13
|
+
* must do this before comparing raw route/config/basePath identity so those
|
|
14
|
+
* comparisons agree with the `Request` that userland eventually receives.
|
|
15
|
+
*/
|
|
16
|
+
declare function canonicalizeRequestPathname(pathname: string): string;
|
|
17
|
+
/** Canonicalize only the pathname portion while preserving the raw query. */
|
|
18
|
+
declare function canonicalizeRequestUrlPathname(url: string): string;
|
|
8
19
|
/**
|
|
9
20
|
* Shared request pipeline utilities.
|
|
10
21
|
*
|
|
@@ -44,18 +55,6 @@ import { hasBasePath, stripBasePath } from "../utils/base-path.js";
|
|
|
44
55
|
*/
|
|
45
56
|
declare function guardProtocolRelativeUrl(rawPathname: string): Response | null;
|
|
46
57
|
type HeaderRecord = Record<string, string | string[]>;
|
|
47
|
-
type ApplyConfigHeadersOptions = {
|
|
48
|
-
configHeaders: NextHeader[];
|
|
49
|
-
pathname: string;
|
|
50
|
-
requestContext: RequestContext;
|
|
51
|
-
/**
|
|
52
|
-
* basePath gating state. When omitted, every rule is treated as a default
|
|
53
|
-
* (basePath: true) rule for backward compatibility — callers that need to
|
|
54
|
-
* support `basePath: false` headers must pass this in.
|
|
55
|
-
*/
|
|
56
|
-
basePathState?: BasePathMatchState; /** Existing framework-generated headers that matching config rules may replace. */
|
|
57
|
-
overwriteExisting?: ReadonlySet<string>;
|
|
58
|
-
};
|
|
59
58
|
type StaticFileSignalContext = {
|
|
60
59
|
headers: Headers | null;
|
|
61
60
|
status: number | null;
|
|
@@ -67,19 +66,6 @@ type ResolvePublicFileRouteOptions = {
|
|
|
67
66
|
publicFiles: ReadonlySet<string>;
|
|
68
67
|
request: Request;
|
|
69
68
|
};
|
|
70
|
-
/**
|
|
71
|
-
* Apply matched next.config.js headers to a Web Headers object.
|
|
72
|
-
*
|
|
73
|
-
* Next.js evaluates config header match conditions against the original
|
|
74
|
-
* request snapshot. Middleware response headers still win for the same
|
|
75
|
-
* response key, while multi-value headers are additive.
|
|
76
|
-
*/
|
|
77
|
-
declare function applyConfigHeadersToResponse(responseHeaders: Headers, options: ApplyConfigHeadersOptions): void;
|
|
78
|
-
/**
|
|
79
|
-
* Apply matched next.config.js headers to the early response header record used
|
|
80
|
-
* by Node and Worker Pages Router pipelines before a concrete response exists.
|
|
81
|
-
*/
|
|
82
|
-
declare function applyConfigHeadersToHeaderRecord(headers: HeaderRecord, options: ApplyConfigHeadersOptions): void;
|
|
83
69
|
declare function createStaticFileSignal(pathname: string, context: StaticFileSignalContext): Response;
|
|
84
70
|
/**
|
|
85
71
|
* Resolve the public/ filesystem-route slot in the Next.js routing order.
|
|
@@ -184,4 +170,4 @@ declare function cloneRequestWithHeaders(request: Request, headers: Headers): Re
|
|
|
184
170
|
*/
|
|
185
171
|
declare function cloneRequestWithUrl(request: Request, url: string): Request;
|
|
186
172
|
//#endregion
|
|
187
|
-
export { HeaderRecord, INTERNAL_HEADERS, VINEXT_INTERNAL_HEADERS,
|
|
173
|
+
export { HeaderRecord, INTERNAL_HEADERS, VINEXT_INTERNAL_HEADERS, canonicalizeRequestPathname, canonicalizeRequestUrlPathname, cloneRequestWithHeaders, cloneRequestWithUrl, createStaticFileSignal, filterInternalHeaders, guardProtocolRelativeUrl, hasBasePath, isOpenRedirectShaped, isOriginAllowed, normalizeTrailingSlash, normalizeTrailingSlashPathname, processMiddlewareHeaders, resolvePublicFileRoute, stripBasePath, validateCsrfOrigin, validateServerActionPayload };
|
|
@@ -1,10 +1,32 @@
|
|
|
1
1
|
import { hasBasePath, removeTrailingSlash, stripBasePath } from "../utils/base-path.js";
|
|
2
2
|
import "../utils/protocol-headers.js";
|
|
3
3
|
import { INTERNAL_HEADERS, VINEXT_INTERNAL_HEADERS, VINEXT_STATIC_FILE_HEADER } from "./headers.js";
|
|
4
|
-
import { matchHeaders } from "../config/config-matchers.js";
|
|
5
4
|
import { forbiddenResponse, notFoundResponse } from "./http-error-responses.js";
|
|
6
5
|
import { isOpenRedirectShaped } from "./open-redirect.js";
|
|
7
6
|
//#region src/server/request-pipeline.ts
|
|
7
|
+
const PATHNAME_CANONICALIZATION_BASE = new URL("http://vinext.invalid/");
|
|
8
|
+
/**
|
|
9
|
+
* Apply the URL Standard's pathname canonicalization without decoding and
|
|
10
|
+
* re-encoding ordinary percent escapes.
|
|
11
|
+
*
|
|
12
|
+
* In particular, WHATWG URLs remove literal and percent-encoded dot segments
|
|
13
|
+
* (`/%2e/about` becomes `/about`) while preserving unrelated spellings such
|
|
14
|
+
* as `/%61bout`, `%2F`, `%5C`, and `%252F` byte-for-byte. Node request adapters
|
|
15
|
+
* must do this before comparing raw route/config/basePath identity so those
|
|
16
|
+
* comparisons agree with the `Request` that userland eventually receives.
|
|
17
|
+
*/
|
|
18
|
+
function canonicalizeRequestPathname(pathname) {
|
|
19
|
+
const url = new URL(PATHNAME_CANONICALIZATION_BASE);
|
|
20
|
+
url.pathname = pathname;
|
|
21
|
+
return url.pathname;
|
|
22
|
+
}
|
|
23
|
+
/** Canonicalize only the pathname portion while preserving the raw query. */
|
|
24
|
+
function canonicalizeRequestUrlPathname(url) {
|
|
25
|
+
const queryIndex = url.indexOf("?");
|
|
26
|
+
const pathname = queryIndex === -1 ? url : url.slice(0, queryIndex);
|
|
27
|
+
const search = queryIndex === -1 ? "" : url.slice(queryIndex);
|
|
28
|
+
return canonicalizeRequestPathname(pathname) + search;
|
|
29
|
+
}
|
|
8
30
|
/**
|
|
9
31
|
* Shared request pipeline utilities.
|
|
10
32
|
*
|
|
@@ -50,63 +72,6 @@ const FILE_LIKE_PATHNAME_RE = /\.[^/]+\/?$/;
|
|
|
50
72
|
function isWellKnownPathname(pathname) {
|
|
51
73
|
return pathname === "/.well-known" || pathname.startsWith("/.well-known/");
|
|
52
74
|
}
|
|
53
|
-
function findHeaderRecordKey(headers, lowerName) {
|
|
54
|
-
for (const key of Object.keys(headers)) if (key.toLowerCase() === lowerName) return key;
|
|
55
|
-
}
|
|
56
|
-
function appendHeaderRecord(headers, lowerName, value) {
|
|
57
|
-
const key = findHeaderRecordKey(headers, lowerName) ?? lowerName;
|
|
58
|
-
const existing = headers[key];
|
|
59
|
-
if (existing === void 0) {
|
|
60
|
-
headers[key] = value;
|
|
61
|
-
return;
|
|
62
|
-
}
|
|
63
|
-
if (Array.isArray(existing)) {
|
|
64
|
-
existing.push(value);
|
|
65
|
-
return;
|
|
66
|
-
}
|
|
67
|
-
headers[key] = [existing, value];
|
|
68
|
-
}
|
|
69
|
-
function appendVaryHeaderRecord(headers, value) {
|
|
70
|
-
const key = findHeaderRecordKey(headers, "vary") ?? "vary";
|
|
71
|
-
const existing = headers[key];
|
|
72
|
-
if (existing === void 0) {
|
|
73
|
-
headers[key] = value;
|
|
74
|
-
return;
|
|
75
|
-
}
|
|
76
|
-
if (Array.isArray(existing)) {
|
|
77
|
-
existing.push(value);
|
|
78
|
-
return;
|
|
79
|
-
}
|
|
80
|
-
headers[key] = existing + ", " + value;
|
|
81
|
-
}
|
|
82
|
-
/**
|
|
83
|
-
* Apply matched next.config.js headers to a Web Headers object.
|
|
84
|
-
*
|
|
85
|
-
* Next.js evaluates config header match conditions against the original
|
|
86
|
-
* request snapshot. Middleware response headers still win for the same
|
|
87
|
-
* response key, while multi-value headers are additive.
|
|
88
|
-
*/
|
|
89
|
-
function applyConfigHeadersToResponse(responseHeaders, options) {
|
|
90
|
-
const matched = matchHeaders(options.pathname, options.configHeaders, options.requestContext, options.basePathState);
|
|
91
|
-
for (const header of matched) {
|
|
92
|
-
const lowerName = header.key.toLowerCase();
|
|
93
|
-
if (lowerName === "vary" || lowerName === "set-cookie") responseHeaders.append(header.key, header.value);
|
|
94
|
-
else if (options.overwriteExisting?.has(lowerName) || !responseHeaders.has(lowerName)) responseHeaders.set(header.key, header.value);
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
/**
|
|
98
|
-
* Apply matched next.config.js headers to the early response header record used
|
|
99
|
-
* by Node and Worker Pages Router pipelines before a concrete response exists.
|
|
100
|
-
*/
|
|
101
|
-
function applyConfigHeadersToHeaderRecord(headers, options) {
|
|
102
|
-
const matched = matchHeaders(options.pathname, options.configHeaders, options.requestContext, options.basePathState);
|
|
103
|
-
for (const header of matched) {
|
|
104
|
-
const lowerName = header.key.toLowerCase();
|
|
105
|
-
if (lowerName === "set-cookie") appendHeaderRecord(headers, lowerName, header.value);
|
|
106
|
-
else if (lowerName === "vary") appendVaryHeaderRecord(headers, header.value);
|
|
107
|
-
else if (findHeaderRecordKey(headers, lowerName) === void 0) headers[lowerName] = header.value;
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
75
|
function createStaticFileSignal(pathname, context) {
|
|
111
76
|
const headers = new Headers({ [VINEXT_STATIC_FILE_HEADER]: encodeURIComponent(pathname) });
|
|
112
77
|
if (context.headers) for (const [key, value] of context.headers) headers.append(key, value);
|
|
@@ -445,4 +410,4 @@ function cloneRequestWithUrl(request, url) {
|
|
|
445
410
|
return cloned;
|
|
446
411
|
}
|
|
447
412
|
//#endregion
|
|
448
|
-
export { INTERNAL_HEADERS, VINEXT_INTERNAL_HEADERS,
|
|
413
|
+
export { INTERNAL_HEADERS, VINEXT_INTERNAL_HEADERS, canonicalizeRequestPathname, canonicalizeRequestUrlPathname, cloneRequestWithHeaders, cloneRequestWithUrl, createStaticFileSignal, filterInternalHeaders, guardProtocolRelativeUrl, hasBasePath, isOpenRedirectShaped, isOriginAllowed, normalizeTrailingSlash, normalizeTrailingSlashPathname, processMiddlewareHeaders, resolvePublicFileRoute, stripBasePath, validateCsrfOrigin, validateServerActionPayload };
|
|
@@ -1,31 +1,135 @@
|
|
|
1
1
|
//#region src/server/rsc-stream-hints.ts
|
|
2
|
-
const REACT_FLIGHT_STYLESHEET_PRELOAD_HINT =
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
2
|
+
const REACT_FLIGHT_STYLESHEET_PRELOAD_HINT = /^([0-9a-f]*:HL\[.*?),"stylesheet"(\]|,)/;
|
|
3
|
+
const STYLESHEET_TO_STYLE_JSON_PADDING = " ".repeat(5);
|
|
4
|
+
const LENGTH_PREFIXED_ROW_TAGS = /* @__PURE__ */ new Set([
|
|
5
|
+
"T",
|
|
6
|
+
"A",
|
|
7
|
+
"O",
|
|
8
|
+
"o",
|
|
9
|
+
"b",
|
|
10
|
+
"U",
|
|
11
|
+
"S",
|
|
12
|
+
"s",
|
|
13
|
+
"L",
|
|
14
|
+
"l",
|
|
15
|
+
"G",
|
|
16
|
+
"g",
|
|
17
|
+
"M",
|
|
18
|
+
"m",
|
|
19
|
+
"V"
|
|
20
|
+
]);
|
|
21
|
+
const NEWLINE_PREFIXED_ROW_TAGS = /* @__PURE__ */ new Set([
|
|
22
|
+
"I",
|
|
23
|
+
"H",
|
|
24
|
+
"E",
|
|
25
|
+
"N",
|
|
26
|
+
"D",
|
|
27
|
+
"J",
|
|
28
|
+
"W",
|
|
29
|
+
"R",
|
|
30
|
+
"r",
|
|
31
|
+
"X",
|
|
32
|
+
"x",
|
|
33
|
+
"C",
|
|
34
|
+
"P",
|
|
35
|
+
"#"
|
|
36
|
+
]);
|
|
37
|
+
const decoder = new TextDecoder();
|
|
38
|
+
const encoder = new TextEncoder();
|
|
39
|
+
/** Rewrite only a complete React Flight stylesheet hint row. */
|
|
8
40
|
function normalizeReactFlightHintLine(line) {
|
|
9
|
-
|
|
41
|
+
const text = decoder.decode(line);
|
|
42
|
+
const normalized = text.replace(REACT_FLIGHT_STYLESHEET_PRELOAD_HINT, `$1,"style"${STYLESHEET_TO_STYLE_JSON_PADDING}$2`);
|
|
43
|
+
if (normalized === text) return line;
|
|
44
|
+
const normalizedBytes = encoder.encode(normalized);
|
|
45
|
+
return normalizedBytes.byteLength === line.byteLength ? normalizedBytes : line;
|
|
46
|
+
}
|
|
47
|
+
function concatBytes(first, second) {
|
|
48
|
+
if (first.byteLength === 0) return second;
|
|
49
|
+
const combined = new Uint8Array(first.byteLength + second.byteLength);
|
|
50
|
+
combined.set(first);
|
|
51
|
+
combined.set(second, first.byteLength);
|
|
52
|
+
return combined;
|
|
53
|
+
}
|
|
54
|
+
function indexOfByte(bytes, byte, from = 0) {
|
|
55
|
+
for (let index = from; index < bytes.byteLength; index++) if (bytes[index] === byte) return index;
|
|
56
|
+
return -1;
|
|
57
|
+
}
|
|
58
|
+
function parseHexBytes(bytes, start, end) {
|
|
59
|
+
if (start === end) return null;
|
|
60
|
+
let value = 0;
|
|
61
|
+
for (let index = start; index < end; index++) {
|
|
62
|
+
const byte = bytes[index];
|
|
63
|
+
const digit = byte >= 48 && byte <= 57 ? byte - 48 : byte >= 97 && byte <= 102 ? byte - 87 : -1;
|
|
64
|
+
if (digit === -1) return null;
|
|
65
|
+
value = value * 16 + digit;
|
|
66
|
+
if (!Number.isSafeInteger(value)) return null;
|
|
67
|
+
}
|
|
68
|
+
return value;
|
|
69
|
+
}
|
|
70
|
+
function isUntaggedJsonRowStart(byte) {
|
|
71
|
+
return byte === 34 || byte === 45 || byte >= 48 && byte <= 57 || byte === 91 || byte === 102 || byte === 110 || byte === 116 || byte === 123;
|
|
10
72
|
}
|
|
11
73
|
function normalizeReactFlightPreloadHints(stream) {
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
let
|
|
74
|
+
let carry = /* @__PURE__ */ new Uint8Array();
|
|
75
|
+
let rawBytesRemaining = 0;
|
|
76
|
+
let passThrough = false;
|
|
15
77
|
return stream.pipeThrough(new TransformStream({
|
|
16
78
|
transform(chunk, controller) {
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
if (lastNewline === -1) {
|
|
20
|
-
carry = text;
|
|
79
|
+
if (passThrough) {
|
|
80
|
+
controller.enqueue(chunk);
|
|
21
81
|
return;
|
|
22
82
|
}
|
|
23
|
-
|
|
24
|
-
|
|
83
|
+
let bytes = concatBytes(carry, chunk);
|
|
84
|
+
carry = /* @__PURE__ */ new Uint8Array();
|
|
85
|
+
while (bytes.byteLength > 0) {
|
|
86
|
+
if (rawBytesRemaining > 0) {
|
|
87
|
+
const length = Math.min(rawBytesRemaining, bytes.byteLength);
|
|
88
|
+
controller.enqueue(bytes.slice(0, length));
|
|
89
|
+
rawBytesRemaining -= length;
|
|
90
|
+
bytes = bytes.subarray(length);
|
|
91
|
+
continue;
|
|
92
|
+
}
|
|
93
|
+
const colon = indexOfByte(bytes, 58);
|
|
94
|
+
if (colon === -1 || colon + 1 === bytes.byteLength) {
|
|
95
|
+
carry = bytes.slice();
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
const tag = String.fromCharCode(bytes[colon + 1]);
|
|
99
|
+
if (LENGTH_PREFIXED_ROW_TAGS.has(tag)) {
|
|
100
|
+
const comma = indexOfByte(bytes, 44, colon + 2);
|
|
101
|
+
if (comma === -1) {
|
|
102
|
+
carry = bytes.slice();
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
const length = parseHexBytes(bytes, colon + 2, comma);
|
|
106
|
+
if (length != null) {
|
|
107
|
+
controller.enqueue(bytes.slice(0, comma + 1));
|
|
108
|
+
rawBytesRemaining = length;
|
|
109
|
+
bytes = bytes.subarray(comma + 1);
|
|
110
|
+
continue;
|
|
111
|
+
}
|
|
112
|
+
passThrough = true;
|
|
113
|
+
controller.enqueue(bytes);
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
const tagByte = bytes[colon + 1];
|
|
117
|
+
if (!NEWLINE_PREFIXED_ROW_TAGS.has(tag) && !isUntaggedJsonRowStart(tagByte)) {
|
|
118
|
+
passThrough = true;
|
|
119
|
+
controller.enqueue(bytes);
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
const newline = indexOfByte(bytes, 10);
|
|
123
|
+
if (newline === -1) {
|
|
124
|
+
carry = bytes.slice();
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
controller.enqueue(normalizeReactFlightHintLine(bytes.slice(0, newline + 1)));
|
|
128
|
+
bytes = bytes.subarray(newline + 1);
|
|
129
|
+
}
|
|
25
130
|
},
|
|
26
131
|
flush(controller) {
|
|
27
|
-
|
|
28
|
-
if (text) controller.enqueue(encoder.encode(normalizeReactFlightHintLine(text)));
|
|
132
|
+
if (carry.byteLength > 0) controller.enqueue(rawBytesRemaining > 0 ? carry : normalizeReactFlightHintLine(carry));
|
|
29
133
|
}
|
|
30
134
|
}));
|
|
31
135
|
}
|