vinext 0.1.3 → 0.1.5
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/build/client-build-config.d.ts +11 -2
- package/dist/build/client-build-config.js +17 -6
- package/dist/build/css-url-assets.d.ts +1 -1
- package/dist/build/css-url-assets.js +9 -7
- package/dist/build/prerender.js +3 -1
- package/dist/cache/cache-adapters-virtual.js +1 -1
- package/dist/client/pages-router-link-navigation.d.ts +33 -7
- package/dist/client/pages-router-link-navigation.js +32 -2
- package/dist/client/vinext-next-data.js +2 -0
- package/dist/cloudflare/src/cache/kv-data-adapter.runtime.d.ts +1 -1
- package/dist/config/config-matchers.d.ts +11 -1
- package/dist/config/config-matchers.js +14 -2
- package/dist/config/tsconfig-paths.js +14 -1
- package/dist/deploy.js +20 -13
- package/dist/entries/app-rsc-entry.js +27 -22
- package/dist/entries/pages-client-entry.js +14 -13
- package/dist/entries/pages-server-entry.js +8 -27
- package/dist/index.js +365 -147
- package/dist/plugins/css-data-url.js +30 -26
- package/dist/plugins/dynamic-preload-metadata.js +2 -4
- package/dist/plugins/extensionless-dynamic-import.js +27 -24
- package/dist/plugins/fonts.js +5 -4
- package/dist/plugins/import-meta-url.js +21 -15
- package/dist/plugins/instrumentation-client.js +1 -1
- package/dist/plugins/middleware-server-only.js +7 -6
- package/dist/plugins/og-assets.js +48 -46
- package/dist/plugins/optimize-imports.js +9 -3
- package/dist/plugins/remove-console.d.ts +7 -1
- package/dist/plugins/remove-console.js +4 -1
- package/dist/plugins/require-context.js +21 -20
- package/dist/plugins/strip-server-exports.d.ts +16 -8
- package/dist/plugins/strip-server-exports.js +496 -46
- package/dist/routing/app-route-graph.js +2 -2
- package/dist/server/app-bfcache-identity.d.ts +26 -0
- package/dist/server/app-bfcache-identity.js +127 -0
- package/dist/server/app-browser-action-result.js +1 -1
- package/dist/server/app-browser-entry.js +22 -12
- package/dist/server/app-browser-navigation-controller.d.ts +1 -1
- package/dist/server/app-browser-navigation-controller.js +1 -1
- package/dist/server/app-browser-state.d.ts +3 -22
- package/dist/server/app-browser-state.js +23 -139
- package/dist/server/app-browser-stream.js +1 -1
- package/dist/server/app-browser-visible-commit.d.ts +1 -1
- package/dist/server/app-browser-visible-commit.js +3 -2
- package/dist/server/app-fallback-renderer.d.ts +1 -1
- package/dist/server/app-layout-param-observation.d.ts +1 -1
- package/dist/server/app-layout-param-observation.js +1 -1
- package/dist/server/app-middleware.js +2 -1
- package/dist/server/app-page-boundary-render.d.ts +1 -1
- package/dist/server/app-page-boundary.js +1 -1
- package/dist/server/app-page-cache-finalizer.d.ts +62 -0
- package/dist/server/app-page-cache-finalizer.js +122 -0
- package/dist/server/app-page-cache-render.d.ts +2 -2
- package/dist/server/app-page-cache-render.js +1 -1
- package/dist/server/app-page-cache.d.ts +2 -53
- package/dist/server/app-page-cache.js +5 -131
- package/dist/server/app-page-dispatch.d.ts +2 -2
- package/dist/server/app-page-dispatch.js +10 -8
- package/dist/server/app-page-probe.js +3 -2
- package/dist/server/app-page-render-observation.js +2 -2
- package/dist/server/app-page-render.d.ts +3 -3
- package/dist/server/app-page-render.js +3 -2
- package/dist/server/app-page-stream.d.ts +2 -9
- package/dist/server/app-page-stream.js +1 -35
- package/dist/server/app-pages-bridge.d.ts +5 -1
- package/dist/server/app-pages-bridge.js +5 -13
- package/dist/server/app-request-context.d.ts +1 -2
- package/dist/server/app-request-context.js +2 -1
- package/dist/server/app-route-handler-dispatch.js +3 -2
- package/dist/server/app-route-handler-execution.d.ts +1 -1
- package/dist/server/app-route-handler-execution.js +1 -1
- package/dist/server/app-route-handler-response.d.ts +1 -1
- package/dist/server/app-router-entry.js +2 -1
- package/dist/server/app-rsc-handler.d.ts +3 -0
- package/dist/server/app-rsc-handler.js +73 -31
- package/dist/server/app-rsc-response-finalizer.js +1 -1
- package/dist/server/app-rsc-route-matching.js +6 -2
- package/dist/server/app-server-action-execution.d.ts +1 -1
- package/dist/server/app-server-action-execution.js +10 -6
- package/dist/server/app-ssr-entry.d.ts +1 -1
- package/dist/server/app-ssr-entry.js +12 -38
- package/dist/server/app-ssr-router-instance.d.ts +6 -0
- package/dist/server/app-ssr-router-instance.js +24 -0
- package/dist/server/app-ssr-stream.js +1 -1
- package/dist/server/artifact-compatibility.js +1 -1
- package/dist/server/before-interactive-head.d.ts +17 -0
- package/dist/server/before-interactive-head.js +35 -0
- package/dist/server/client-reuse-manifest.js +1 -1
- package/dist/server/csp.js +1 -4
- package/dist/server/defer-until-stream-consumed.d.ts +7 -0
- package/dist/server/defer-until-stream-consumed.js +34 -0
- package/dist/server/dev-server.js +82 -37
- package/dist/server/instrumentation.js +1 -1
- package/dist/server/isr-cache.d.ts +1 -1
- package/dist/server/isr-cache.js +1 -1
- package/dist/server/isr-decision.d.ts +1 -1
- package/dist/server/middleware-matcher.js +20 -9
- package/dist/server/middleware-runtime.d.ts +3 -4
- package/dist/server/middleware-runtime.js +4 -2
- package/dist/server/navigation-planner.d.ts +3 -12
- package/dist/server/navigation-planner.js +24 -0
- package/dist/server/navigation-trace.d.ts +2 -1
- package/dist/server/navigation-trace.js +1 -0
- package/dist/server/open-redirect.d.ts +12 -0
- package/dist/server/open-redirect.js +21 -0
- package/dist/server/operation-token.d.ts +40 -0
- package/dist/server/operation-token.js +85 -0
- package/dist/server/pages-data-route.d.ts +1 -1
- package/dist/server/pages-data-route.js +7 -4
- package/dist/server/pages-dev-module-url.d.ts +4 -0
- package/dist/server/pages-dev-module-url.js +15 -0
- package/dist/server/pages-document-initial-props.d.ts +4 -15
- package/dist/server/pages-document-initial-props.js +27 -56
- package/dist/server/pages-i18n.js +2 -2
- package/dist/server/pages-page-data.d.ts +1 -1
- package/dist/server/pages-page-data.js +3 -1
- package/dist/server/pages-page-handler.js +3 -1
- package/dist/server/pages-page-response.d.ts +3 -1
- package/dist/server/pages-page-response.js +6 -6
- package/dist/server/pages-readiness.js +1 -1
- package/dist/server/pages-request-pipeline.d.ts +7 -7
- package/dist/server/pages-request-pipeline.js +63 -21
- package/dist/server/prod-server.d.ts +3 -1
- package/dist/server/prod-server.js +43 -11
- package/dist/server/request-pipeline.d.ts +1 -24
- package/dist/server/request-pipeline.js +1 -33
- package/dist/server/seed-cache.d.ts +1 -1
- package/dist/server/static-file-cache.js +16 -4
- package/dist/shims/before-interactive-context.d.ts +14 -3
- package/dist/shims/cache-handler.d.ts +106 -0
- package/dist/shims/cache-handler.js +176 -0
- package/dist/shims/cache-request-state.d.ts +47 -0
- package/dist/shims/cache-request-state.js +126 -0
- package/dist/shims/cache-runtime.d.ts +2 -2
- package/dist/shims/cache-runtime.js +3 -14
- package/dist/shims/cache.d.ts +3 -231
- package/dist/shims/cache.js +17 -383
- package/dist/shims/cdn-cache.d.ts +1 -1
- package/dist/shims/cdn-cache.js +1 -1
- package/dist/shims/document.d.ts +15 -20
- package/dist/shims/document.js +5 -8
- package/dist/shims/error-boundary-navigation.d.ts +7 -0
- package/dist/shims/error-boundary-navigation.js +44 -0
- package/dist/shims/error-boundary.js +10 -8
- package/dist/shims/error.js +2 -1
- package/dist/shims/fetch-cache.js +1 -1
- package/dist/shims/form.js +1 -1
- package/dist/shims/image.js +74 -9
- package/dist/shims/internal/app-page-props-cache-key.d.ts +5 -0
- package/dist/shims/internal/app-page-props-cache-key.js +16 -0
- package/dist/shims/internal/navigation-untracked.js +2 -1
- package/dist/shims/internal/pages-data-fetch-dedup.d.ts +6 -7
- package/dist/shims/internal/pages-data-fetch-dedup.js +67 -14
- package/dist/shims/internal/pages-data-target.js +1 -1
- package/dist/shims/layout-segment-context.d.ts +1 -1
- package/dist/shims/layout-segment-context.js +2 -1
- package/dist/shims/link.js +38 -17
- package/dist/shims/metadata.js +4 -4
- package/dist/shims/navigation-context-state.d.ts +40 -0
- package/dist/shims/navigation-context-state.js +116 -0
- package/dist/shims/navigation-errors.d.ts +55 -0
- package/dist/shims/navigation-errors.js +110 -0
- package/dist/shims/navigation-server.d.ts +3 -0
- package/dist/shims/navigation-server.js +3 -0
- package/dist/shims/navigation-state.d.ts +1 -2
- package/dist/shims/navigation-state.js +2 -1
- package/dist/shims/navigation.d.ts +3 -291
- package/dist/shims/navigation.js +16 -445
- package/dist/shims/navigation.react-server.d.ts +2 -2
- package/dist/shims/navigation.react-server.js +3 -1
- package/dist/shims/request-state-types.d.ts +3 -3
- package/dist/shims/router.d.ts +6 -2
- package/dist/shims/router.js +99 -20
- package/dist/shims/script.js +9 -5
- package/dist/shims/slot.js +3 -1
- package/dist/shims/unified-request-context.d.ts +2 -2
- package/dist/utils/has-trailing-comma.d.ts +24 -0
- package/dist/utils/has-trailing-comma.js +62 -0
- package/dist/utils/text-stream.d.ts +1 -1
- package/dist/utils/text-stream.js +2 -2
- package/dist/utils/virtual-module.d.ts +5 -0
- package/dist/utils/virtual-module.js +0 -0
- package/dist/utils/vite-version.d.ts +12 -1
- package/dist/utils/vite-version.js +9 -1
- package/package.json +5 -1
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { assertSafeNavigationUrl } from "../shims/url-safety.js";
|
|
2
|
+
import "./app-bfcache-id.js";
|
|
3
|
+
//#region src/server/app-ssr-router-instance.ts
|
|
4
|
+
function validateNavigationHref(href) {
|
|
5
|
+
assertSafeNavigationUrl(href);
|
|
6
|
+
}
|
|
7
|
+
const ssrAppRouterInstance = {
|
|
8
|
+
bfcacheId: "0",
|
|
9
|
+
back() {},
|
|
10
|
+
forward() {},
|
|
11
|
+
refresh() {},
|
|
12
|
+
push(href, _options) {
|
|
13
|
+
validateNavigationHref(href);
|
|
14
|
+
},
|
|
15
|
+
replace(href, _options) {
|
|
16
|
+
validateNavigationHref(href);
|
|
17
|
+
},
|
|
18
|
+
prefetch(href) {
|
|
19
|
+
validateNavigationHref(href);
|
|
20
|
+
}
|
|
21
|
+
};
|
|
22
|
+
if (process.env.__NEXT_GESTURE_TRANSITION) ssrAppRouterInstance.experimental_gesturePush = validateNavigationHref;
|
|
23
|
+
//#endregion
|
|
24
|
+
export { ssrAppRouterInstance };
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { NAVIGATION_RUNTIME_SYMBOL_DESCRIPTION } from "../client/navigation-runtime.js";
|
|
2
1
|
import { createInlineScriptTag, escapeHtmlAttr, htmlTokenListContains, safeJsonStringify } from "./html.js";
|
|
3
2
|
import { bytesToBase64, concatUint8Arrays } from "./app-rsc-embedded-chunks.js";
|
|
3
|
+
import { NAVIGATION_RUNTIME_SYMBOL_DESCRIPTION } from "../client/navigation-runtime.js";
|
|
4
4
|
//#region src/server/app-ssr-stream.ts
|
|
5
5
|
const NAVIGATION_RUNTIME_REFERENCE = `self[Symbol.for(${safeJsonStringify(NAVIGATION_RUNTIME_SYMBOL_DESCRIPTION)})]`;
|
|
6
6
|
function navigationRuntimeRscBootstrapExpression() {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { fnv1a64 } from "../utils/hash.js";
|
|
2
1
|
import { isUnknownRecord } from "../utils/record.js";
|
|
2
|
+
import { fnv1a64 } from "../utils/hash.js";
|
|
3
3
|
//#region src/server/artifact-compatibility.ts
|
|
4
4
|
const ARTIFACT_COMPATIBILITY_SCHEMA_VERSION = 1;
|
|
5
5
|
const APP_ELEMENTS_SCHEMA_VERSION = 1;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { BeforeInteractiveInlineScript } from "../shims/before-interactive-context.js";
|
|
2
|
+
|
|
3
|
+
//#region src/server/before-interactive-head.d.ts
|
|
4
|
+
/**
|
|
5
|
+
* Render captured `<Script strategy="beforeInteractive">` scripts to HTML,
|
|
6
|
+
* ready to splice immediately after `<head ...>` opens. Each entry has already
|
|
7
|
+
* had its inline content escaped via `escapeInlineContent(..., "script")`
|
|
8
|
+
* inside the Script shim, so this function only quotes the attributes that
|
|
9
|
+
* actually go on the tag (id, src, nonce, plus the residual passthroughs).
|
|
10
|
+
*
|
|
11
|
+
* Keeping this function in its own module makes the boundary obvious: anything
|
|
12
|
+
* passed through here is being concatenated directly into HTML; treat the
|
|
13
|
+
* inputs accordingly.
|
|
14
|
+
*/
|
|
15
|
+
declare function renderBeforeInteractiveInlineScripts(scripts: readonly BeforeInteractiveInlineScript[]): string;
|
|
16
|
+
//#endregion
|
|
17
|
+
export { renderBeforeInteractiveInlineScripts };
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { createNonceAttribute, escapeHtmlAttr } from "./html.js";
|
|
2
|
+
//#region src/server/before-interactive-head.ts
|
|
3
|
+
const VALID_ATTR_NAME = /^[a-zA-Z][\w.-]*$/;
|
|
4
|
+
/**
|
|
5
|
+
* Render captured `<Script strategy="beforeInteractive">` scripts to HTML,
|
|
6
|
+
* ready to splice immediately after `<head ...>` opens. Each entry has already
|
|
7
|
+
* had its inline content escaped via `escapeInlineContent(..., "script")`
|
|
8
|
+
* inside the Script shim, so this function only quotes the attributes that
|
|
9
|
+
* actually go on the tag (id, src, nonce, plus the residual passthroughs).
|
|
10
|
+
*
|
|
11
|
+
* Keeping this function in its own module makes the boundary obvious: anything
|
|
12
|
+
* passed through here is being concatenated directly into HTML; treat the
|
|
13
|
+
* inputs accordingly.
|
|
14
|
+
*/
|
|
15
|
+
function renderBeforeInteractiveInlineScripts(scripts) {
|
|
16
|
+
if (scripts.length === 0) return "";
|
|
17
|
+
let html = "";
|
|
18
|
+
for (const script of scripts) {
|
|
19
|
+
let attrs = "";
|
|
20
|
+
if (script.id) attrs += ` id="${escapeHtmlAttr(script.id)}"`;
|
|
21
|
+
if (script.src) attrs += ` src="${escapeHtmlAttr(script.src)}"`;
|
|
22
|
+
attrs += createNonceAttribute(script.nonce);
|
|
23
|
+
if (script.attributes) for (const [key, value] of Object.entries(script.attributes)) {
|
|
24
|
+
if (!VALID_ATTR_NAME.test(key)) continue;
|
|
25
|
+
if (key === "data-nscript") continue;
|
|
26
|
+
if (value === true) attrs += ` ${key}`;
|
|
27
|
+
else if (typeof value === "string") attrs += ` ${key}="${escapeHtmlAttr(value)}"`;
|
|
28
|
+
}
|
|
29
|
+
attrs += ` data-nscript="beforeInteractive"`;
|
|
30
|
+
html += `<script${attrs}>${script.innerHTML ?? ""}<\/script>`;
|
|
31
|
+
}
|
|
32
|
+
return html;
|
|
33
|
+
}
|
|
34
|
+
//#endregion
|
|
35
|
+
export { renderBeforeInteractiveInlineScripts };
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { fnv1a64 } from "../utils/hash.js";
|
|
2
1
|
import { isUnknownRecord } from "../utils/record.js";
|
|
2
|
+
import { fnv1a64 } from "../utils/hash.js";
|
|
3
3
|
import { parseArtifactCompatibilityEnvelope } from "./artifact-compatibility.js";
|
|
4
4
|
import { AppElementsWire } from "./app-elements-wire.js";
|
|
5
5
|
import { isNonNegativeSafeInteger } from "../utils/number.js";
|
package/dist/server/csp.js
CHANGED
|
@@ -1,8 +1,5 @@
|
|
|
1
1
|
//#region src/server/csp.ts
|
|
2
2
|
const ESCAPE_REGEX = /[&><\u2028\u2029]/;
|
|
3
|
-
function matchesDirectiveName(directive, name) {
|
|
4
|
-
return directive === name || directive.startsWith(`${name} `);
|
|
5
|
-
}
|
|
6
3
|
function getNodeHeaderValue(headers, key) {
|
|
7
4
|
const value = headers?.[key];
|
|
8
5
|
if (Array.isArray(value)) return value.join(", ");
|
|
@@ -11,7 +8,7 @@ function getNodeHeaderValue(headers, key) {
|
|
|
11
8
|
}
|
|
12
9
|
function getScriptNonceFromHeader(cspHeaderValue) {
|
|
13
10
|
const directives = cspHeaderValue.split(";").map((directive) => directive.trim());
|
|
14
|
-
const directive = directives.find((value) =>
|
|
11
|
+
const directive = directives.find((value) => value.startsWith("script-src")) ?? directives.find((value) => value.startsWith("default-src"));
|
|
15
12
|
if (!directive) return;
|
|
16
13
|
const nonce = directive.split(" ").slice(1).map((source) => source.trim()).find((source) => source.startsWith("'nonce-") && source.length > 8 && source.endsWith("'"))?.slice(7, -1);
|
|
17
14
|
if (!nonce) return;
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
//#region src/server/defer-until-stream-consumed.d.ts
|
|
2
|
+
/**
|
|
3
|
+
* Defers cleanup until the downstream consumer drains or cancels the stream.
|
|
4
|
+
*/
|
|
5
|
+
declare function deferUntilStreamConsumed(stream: ReadableStream<Uint8Array>, onFlush: () => void): ReadableStream<Uint8Array>;
|
|
6
|
+
//#endregion
|
|
7
|
+
export { deferUntilStreamConsumed };
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
//#region src/server/defer-until-stream-consumed.ts
|
|
2
|
+
/**
|
|
3
|
+
* Defers cleanup until the downstream consumer drains or cancels the stream.
|
|
4
|
+
*/
|
|
5
|
+
function deferUntilStreamConsumed(stream, onFlush) {
|
|
6
|
+
let called = false;
|
|
7
|
+
const once = () => {
|
|
8
|
+
if (!called) {
|
|
9
|
+
called = true;
|
|
10
|
+
onFlush();
|
|
11
|
+
}
|
|
12
|
+
};
|
|
13
|
+
const cleanup = new TransformStream({ flush() {
|
|
14
|
+
once();
|
|
15
|
+
} });
|
|
16
|
+
const reader = stream.pipeThrough(cleanup).getReader();
|
|
17
|
+
return new ReadableStream({
|
|
18
|
+
pull(controller) {
|
|
19
|
+
return reader.read().then(({ done, value }) => {
|
|
20
|
+
if (done) controller.close();
|
|
21
|
+
else controller.enqueue(value);
|
|
22
|
+
}, (error) => {
|
|
23
|
+
once();
|
|
24
|
+
controller.error(error);
|
|
25
|
+
});
|
|
26
|
+
},
|
|
27
|
+
cancel(reason) {
|
|
28
|
+
once();
|
|
29
|
+
return reader.cancel(reason);
|
|
30
|
+
}
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
//#endregion
|
|
34
|
+
export { deferUntilStreamConsumed };
|
|
@@ -7,10 +7,10 @@ import { normalizeStaticPathname } from "../routing/route-pattern.js";
|
|
|
7
7
|
import { importModule, reportRequestError } from "./instrumentation.js";
|
|
8
8
|
import { buildCacheStateHeaders } from "./cache-headers.js";
|
|
9
9
|
import { isUnknownRecord } from "../utils/record.js";
|
|
10
|
-
import { _runWithCacheState } from "../shims/cache.js";
|
|
11
10
|
import { NEVER_CACHE_CONTROL, NO_STORE_CACHE_CONTROL } from "./cache-control.js";
|
|
12
11
|
import { buildMissIsrCacheControl, decideIsr } from "./isr-decision.js";
|
|
13
12
|
import { PRERENDER_REVALIDATE_HEADER, buildPagesCacheValue, getRevalidateDuration, isOnDemandRevalidateRequest, isrCacheKey, isrGet, isrSet, setRevalidateDuration, triggerBackgroundRegeneration } from "./isr-cache.js";
|
|
13
|
+
import { _runWithCacheState } from "../shims/cache-request-state.js";
|
|
14
14
|
import { ensureFetchPatch, runWithFetchCache } from "../shims/fetch-cache.js";
|
|
15
15
|
import { runWithPrivateCache } from "../shims/cache-runtime.js";
|
|
16
16
|
import { mergeRouteParamsIntoQuery, parseQueryString } from "../utils/query.js";
|
|
@@ -26,6 +26,7 @@ import { detectLocaleFromAcceptLanguage, extractLocaleFromUrl as extractLocaleFr
|
|
|
26
26
|
import { buildDefaultPagesNotFoundResponse } from "./pages-default-404.js";
|
|
27
27
|
import { buildPagesReadinessNextData } from "./pages-readiness.js";
|
|
28
28
|
import { resolvePagesPageMethodResponse } from "./pages-page-method.js";
|
|
29
|
+
import { createPagesDevModuleUrl } from "./pages-dev-module-url.js";
|
|
29
30
|
import { isSerializableProps } from "./pages-serializable-props.js";
|
|
30
31
|
import { loadUserDocumentInitialProps, runDocumentRenderPage } from "./pages-document-initial-props.js";
|
|
31
32
|
import { callDocumentGetInitialProps } from "./document-initial-head.js";
|
|
@@ -100,7 +101,7 @@ const STREAM_BODY_MARKER = "<!--VINEXT_STREAM_BODY-->";
|
|
|
100
101
|
* shell sooner).
|
|
101
102
|
*/
|
|
102
103
|
async function streamPageToResponse(res, element, options) {
|
|
103
|
-
const { url, server, fontHeadHTML, scripts, DocumentComponent, statusCode = 200, extraHeaders, getHeadHTML, enhancePageElement, scriptNonce, documentContext, setDocumentInitialHead } = options;
|
|
104
|
+
const { url, server, fontHeadHTML, scripts, DocumentComponent, statusCode = 200, extraHeaders, getHeadHTML, enhancePageElement, scriptNonce, documentContext, setDocumentInitialHead, bufferBodyBeforeHeaders = false } = options;
|
|
104
105
|
const documentRenderPage = await runDocumentRenderPage({
|
|
105
106
|
DocumentComponent,
|
|
106
107
|
enhancePageElement,
|
|
@@ -144,6 +145,7 @@ async function streamPageToResponse(res, element, options) {
|
|
|
144
145
|
const markerIdx = transformedShell.indexOf(STREAM_BODY_MARKER);
|
|
145
146
|
const prefix = transformedShell.slice(0, markerIdx);
|
|
146
147
|
const suffix = transformedShell.slice(markerIdx + 25);
|
|
148
|
+
const bufferedBody = bufferBodyBeforeHeaders ? await new Response(bodyStream).text() : null;
|
|
147
149
|
const headers = {
|
|
148
150
|
"Content-Type": "text/html",
|
|
149
151
|
"Transfer-Encoding": "chunked"
|
|
@@ -152,6 +154,10 @@ async function streamPageToResponse(res, element, options) {
|
|
|
152
154
|
else headers[key] = val;
|
|
153
155
|
res.writeHead(statusCode, headers);
|
|
154
156
|
res.write(prefix);
|
|
157
|
+
if (bufferedBody !== null) {
|
|
158
|
+
res.end(bufferedBody + suffix);
|
|
159
|
+
return;
|
|
160
|
+
}
|
|
155
161
|
const reader = bodyStream.getReader();
|
|
156
162
|
try {
|
|
157
163
|
for (;;) {
|
|
@@ -622,9 +628,10 @@ function createSSRHandler(server, runner, routes, pagesDir, i18nConfig, fileMatc
|
|
|
622
628
|
}) : React.createElement(pageModule.default, freshPageProps);
|
|
623
629
|
if (routerShim.wrapWithRouterContext) el = routerShim.wrapWithRouterContext(el);
|
|
624
630
|
const freshBody = await renderIsrPassToStringAsync(withScriptNonce(el, scriptNonce));
|
|
625
|
-
const viteRoot = server.config
|
|
626
|
-
const
|
|
627
|
-
const
|
|
631
|
+
const viteRoot = server.config.root;
|
|
632
|
+
const viteBase = server.config.base;
|
|
633
|
+
const regenPageUrl = createPagesDevModuleUrl(viteRoot, route.filePath, viteBase);
|
|
634
|
+
const regenAppUrl = RegenApp ? createPagesDevModuleUrl(viteRoot, path.join(pagesDir, "_app"), viteBase) : null;
|
|
628
635
|
const freshPagesNextData = {
|
|
629
636
|
...pagesNextData,
|
|
630
637
|
__vinext: {
|
|
@@ -634,7 +641,7 @@ function createSSRHandler(server, runner, routes, pagesDir, i18nConfig, fileMatc
|
|
|
634
641
|
hasMiddleware
|
|
635
642
|
}
|
|
636
643
|
};
|
|
637
|
-
await isrSet(cacheKey, buildPagesCacheValue(`<!DOCTYPE html><html><head></head><body><div id="__next">${freshBody}</div>${`<script
|
|
644
|
+
await isrSet(cacheKey, buildPagesCacheValue(`<!DOCTYPE html><html><head></head><body><div id="__next">${freshBody}</div>${`<script id="__NEXT_DATA__" type="application/json">${safeJsonStringify({
|
|
638
645
|
props: freshRenderProps,
|
|
639
646
|
page: patternToNextFormat(route.pattern),
|
|
640
647
|
query: params,
|
|
@@ -645,7 +652,7 @@ function createSSRHandler(server, runner, routes, pagesDir, i18nConfig, fileMatc
|
|
|
645
652
|
defaultLocale: currentDefaultLocale,
|
|
646
653
|
domainLocales,
|
|
647
654
|
...freshPagesNextData
|
|
648
|
-
})}
|
|
655
|
+
})}<\/script>`}\n ${cachedHtml.match(/<script type="module">[\s\S]*?<\/script>/)?.[0] ?? ""}</body></html>`, freshRenderProps), revalidate);
|
|
649
656
|
setRevalidateDuration(cacheKey, revalidate);
|
|
650
657
|
}
|
|
651
658
|
}
|
|
@@ -809,8 +816,11 @@ function createSSRHandler(server, runner, routes, pagesDir, i18nConfig, fileMatc
|
|
|
809
816
|
}
|
|
810
817
|
if (allFontStyles.length > 0) fontHeadHTML += `<style data-vinext-fonts${nonceAttr}>${allFontStyles.join("\n")}</style>\n `;
|
|
811
818
|
const viteRoot = server.config.root;
|
|
812
|
-
const
|
|
813
|
-
const
|
|
819
|
+
const viteBase = server.config.base;
|
|
820
|
+
const pageModuleUrl = createPagesDevModuleUrl(viteRoot, route.filePath, viteBase);
|
|
821
|
+
const pageModuleSource = createPagesDevModuleUrl(viteRoot, route.filePath, "/");
|
|
822
|
+
const appModuleUrl = AppComponent ? createPagesDevModuleUrl(viteRoot, path.join(pagesDir, "_app"), viteBase) : null;
|
|
823
|
+
const appModuleSource = AppComponent ? createPagesDevModuleUrl(viteRoot, path.join(pagesDir, "_app"), "/") : null;
|
|
814
824
|
const serializedPagesNextData = {
|
|
815
825
|
...pagesNextData,
|
|
816
826
|
__vinext: {
|
|
@@ -825,14 +835,22 @@ function createSSRHandler(server, runner, routes, pagesDir, i18nConfig, fileMatc
|
|
|
825
835
|
import "vinext/instrumentation-client";
|
|
826
836
|
import React from "react";
|
|
827
837
|
import { hydrateRoot } from "react-dom/client";
|
|
828
|
-
import Router, { wrapWithRouterContext } from "next/router";
|
|
838
|
+
import Router, { wrapWithRouterContext, _initializePagesRouterReadyFromNextData } from "next/router";
|
|
829
839
|
|
|
840
|
+
const nextDataElement = document.getElementById("__NEXT_DATA__");
|
|
841
|
+
if (nextDataElement?.textContent) {
|
|
842
|
+
window.__NEXT_DATA__ = JSON.parse(nextDataElement.textContent);
|
|
843
|
+
window.__VINEXT_LOCALE__ = window.__NEXT_DATA__.locale;
|
|
844
|
+
window.__VINEXT_LOCALES__ = window.__NEXT_DATA__.locales;
|
|
845
|
+
window.__VINEXT_DEFAULT_LOCALE__ = window.__NEXT_DATA__.defaultLocale;
|
|
846
|
+
}
|
|
830
847
|
const nextData = window.__NEXT_DATA__;
|
|
848
|
+
_initializePagesRouterReadyFromNextData(nextData);
|
|
831
849
|
const props = nextData.props && typeof nextData.props === "object" ? nextData.props : {};
|
|
832
850
|
const rawPageProps = props.pageProps;
|
|
833
851
|
const pageProps = rawPageProps && typeof rawPageProps === "object" ? rawPageProps : {};
|
|
834
|
-
window.__VINEXT_PAGE_LOADERS__ = { [nextData.page]: () => import("${
|
|
835
|
-
window.__VINEXT_APP_LOADER__ = ${
|
|
852
|
+
window.__VINEXT_PAGE_LOADERS__ = { [nextData.page]: () => import("${pageModuleSource}") };
|
|
853
|
+
window.__VINEXT_APP_LOADER__ = ${appModuleSource ? `() => import("${appModuleSource}")` : "undefined"};
|
|
836
854
|
|
|
837
855
|
async function hydrate() {
|
|
838
856
|
let hydrateRootOptions;
|
|
@@ -847,11 +865,11 @@ async function hydrate() {
|
|
|
847
865
|
};
|
|
848
866
|
}
|
|
849
867
|
|
|
850
|
-
const pageModule = await import("${
|
|
868
|
+
const pageModule = await import("${pageModuleSource}");
|
|
851
869
|
const PageComponent = pageModule.default;
|
|
852
870
|
let element;
|
|
853
|
-
${
|
|
854
|
-
const appModule = await import("${
|
|
871
|
+
${appModuleSource ? `
|
|
872
|
+
const appModule = await import("${appModuleSource}");
|
|
855
873
|
const AppComponent = appModule.default;
|
|
856
874
|
window.__VINEXT_APP__ = AppComponent;
|
|
857
875
|
element = React.createElement(AppComponent, {
|
|
@@ -880,7 +898,7 @@ async function hydrate() {
|
|
|
880
898
|
}
|
|
881
899
|
hydrate();
|
|
882
900
|
<\/script>`;
|
|
883
|
-
const nextDataScript =
|
|
901
|
+
const nextDataScript = `<script id="__NEXT_DATA__" type="application/json"${nonceAttr}>${safeJsonStringify({
|
|
884
902
|
props: renderProps,
|
|
885
903
|
page: patternToNextFormat(route.pattern),
|
|
886
904
|
query: params,
|
|
@@ -891,7 +909,7 @@ hydrate();
|
|
|
891
909
|
defaultLocale: currentDefaultLocale,
|
|
892
910
|
domainLocales,
|
|
893
911
|
...serializedPagesNextData
|
|
894
|
-
})}
|
|
912
|
+
})}<\/script>`;
|
|
895
913
|
const docPath = path.join(pagesDir, "_document");
|
|
896
914
|
let DocumentComponent = null;
|
|
897
915
|
if (findFileWithExtensions(docPath, matcher)) try {
|
|
@@ -939,7 +957,8 @@ hydrate();
|
|
|
939
957
|
const traceHTML = getClientTraceMetadataHTML(clientTraceMetadata);
|
|
940
958
|
return traceHTML ? `${headHTML}\n ${traceHTML}` : headHTML;
|
|
941
959
|
},
|
|
942
|
-
setDocumentInitialHead: typeof headShim.setDocumentInitialHead === "function" ? headShim.setDocumentInitialHead : void 0
|
|
960
|
+
setDocumentInitialHead: typeof headShim.setDocumentInitialHead === "function" ? headShim.setDocumentInitialHead : void 0,
|
|
961
|
+
bufferBodyBeforeHeaders: true
|
|
943
962
|
});
|
|
944
963
|
_renderEnd = now();
|
|
945
964
|
if (typeof routerShim.setSSRContext === "function") routerShim.setSSRContext(null);
|
|
@@ -1015,41 +1034,67 @@ async function renderErrorPage(server, runner, req, res, url, pagesDir, statusCo
|
|
|
1015
1034
|
if (!wrapFn) try {
|
|
1016
1035
|
wrapFn = (await importModule(runner, "next/router")).wrapWithRouterContext;
|
|
1017
1036
|
} catch {}
|
|
1018
|
-
let element;
|
|
1019
|
-
if (AppComponent) element = createElement(AppComponent, {
|
|
1020
|
-
Component: ErrorComponent,
|
|
1021
|
-
pageProps: errorProps
|
|
1022
|
-
});
|
|
1023
|
-
else element = createElement(ErrorComponent, errorProps);
|
|
1024
|
-
if (wrapFn) element = wrapFn(element);
|
|
1025
|
-
const bodyHtml = await renderToStringAsync(element);
|
|
1026
|
-
let html;
|
|
1027
1037
|
let DocumentComponent = null;
|
|
1028
1038
|
const docPathErr = path.join(pagesDir, "_document");
|
|
1029
1039
|
if (findFileWithExtensions(docPathErr, matcher)) try {
|
|
1030
1040
|
DocumentComponent = (await importModule(runner, docPathErr)).default ?? null;
|
|
1031
1041
|
} catch {}
|
|
1042
|
+
const createErrorElement = (FinalApp, FinalComponent) => {
|
|
1043
|
+
let errorElement = FinalApp ? createElement(FinalApp, {
|
|
1044
|
+
Component: FinalComponent,
|
|
1045
|
+
pageProps: errorProps
|
|
1046
|
+
}) : createElement(FinalComponent, errorProps);
|
|
1047
|
+
if (wrapFn) errorElement = wrapFn(errorElement);
|
|
1048
|
+
return errorElement;
|
|
1049
|
+
};
|
|
1050
|
+
const element = createErrorElement(AppComponent, ErrorComponent);
|
|
1051
|
+
const headShim = await importModule(runner, "next/head");
|
|
1052
|
+
if (typeof headShim.resetSSRHead === "function") headShim.resetSSRHead();
|
|
1032
1053
|
if (DocumentComponent) {
|
|
1033
|
-
const
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1054
|
+
const errorPathname = candidate === "_error" ? "/_error" : `/${candidate}`;
|
|
1055
|
+
await streamPageToResponse(res, element, {
|
|
1056
|
+
url,
|
|
1057
|
+
server,
|
|
1058
|
+
fontHeadHTML: "",
|
|
1059
|
+
scripts: "",
|
|
1060
|
+
DocumentComponent,
|
|
1061
|
+
statusCode,
|
|
1062
|
+
documentContext: {
|
|
1063
|
+
err,
|
|
1064
|
+
pathname: errorPathname,
|
|
1065
|
+
query: parseQueryString(url),
|
|
1066
|
+
asPath: url,
|
|
1067
|
+
req,
|
|
1068
|
+
res
|
|
1069
|
+
},
|
|
1070
|
+
enhancePageElement: (renderPageOpts) => {
|
|
1071
|
+
let FinalApp = AppComponent;
|
|
1072
|
+
let FinalComponent = ErrorComponent;
|
|
1073
|
+
if (renderPageOpts.enhanceApp && FinalApp) FinalApp = renderPageOpts.enhanceApp(FinalApp);
|
|
1074
|
+
if (renderPageOpts.enhanceComponent) FinalComponent = renderPageOpts.enhanceComponent(FinalComponent);
|
|
1075
|
+
return createErrorElement(FinalApp, FinalComponent);
|
|
1076
|
+
},
|
|
1077
|
+
getHeadHTML: () => typeof headShim.getSSRHeadHTML === "function" ? headShim.getSSRHeadHTML() : "",
|
|
1078
|
+
setDocumentInitialHead: typeof headShim.setDocumentInitialHead === "function" ? headShim.setDocumentInitialHead : void 0
|
|
1079
|
+
});
|
|
1080
|
+
} else {
|
|
1081
|
+
const html = `<!DOCTYPE html>
|
|
1039
1082
|
<html>
|
|
1040
1083
|
<head>
|
|
1041
1084
|
<meta charset="utf-8" />
|
|
1042
1085
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
1043
1086
|
</head>
|
|
1044
1087
|
<body>
|
|
1045
|
-
<div id="__next">${
|
|
1088
|
+
<div id="__next">${await renderToStringAsync(element)}</div>
|
|
1046
1089
|
</body>
|
|
1047
1090
|
</html>`;
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1091
|
+
const transformedHtml = await server.transformIndexHtml(url, html);
|
|
1092
|
+
res.writeHead(statusCode, { "Content-Type": "text/html" });
|
|
1093
|
+
res.end(transformedHtml);
|
|
1094
|
+
}
|
|
1051
1095
|
return;
|
|
1052
1096
|
} catch {
|
|
1097
|
+
if (res.headersSent || res.writableEnded) return;
|
|
1053
1098
|
continue;
|
|
1054
1099
|
}
|
|
1055
1100
|
if (statusCode === 404) {
|
|
@@ -51,7 +51,7 @@ async function importModule(runner, id) {
|
|
|
51
51
|
const INSTRUMENTATION_LOCATIONS = ["", "src/"];
|
|
52
52
|
function findInstrumentationHookFile(root, basename, fileMatcher) {
|
|
53
53
|
for (const dir of INSTRUMENTATION_LOCATIONS) for (const ext of fileMatcher.dottedExtensions) {
|
|
54
|
-
const fullPath = path.join(root, dir, `${basename}${ext}`);
|
|
54
|
+
const fullPath = path.posix.join(root, dir, `${basename}${ext}`);
|
|
55
55
|
if (fs.existsSync(fullPath)) return fullPath;
|
|
56
56
|
}
|
|
57
57
|
return null;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { RenderObservation } from "./cache-proof.js";
|
|
2
|
-
import { CacheHandlerValue, CachedAppPageValue, CachedPagesValue, IncrementalCacheValue } from "../shims/cache.js";
|
|
2
|
+
import { CacheHandlerValue, CachedAppPageValue, CachedPagesValue, IncrementalCacheValue } from "../shims/cache-handler.js";
|
|
3
3
|
import { OnRequestErrorContext } from "./instrumentation.js";
|
|
4
4
|
import { normalizeMountedSlotsHeader } from "./app-mounted-slots-header.js";
|
|
5
5
|
import { AppRscRenderMode } from "./app-rsc-render-mode.js";
|
package/dist/server/isr-cache.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { getRequestExecutionContext } from "../shims/request-context.js";
|
|
2
2
|
import { reportRequestError } from "./instrumentation.js";
|
|
3
|
-
import { fnv1a64 } from "../utils/hash.js";
|
|
4
3
|
import { getCdnCacheAdapter } from "../shims/cdn-cache.js";
|
|
4
|
+
import { fnv1a64 } from "../utils/hash.js";
|
|
5
5
|
import { normalizeMountedSlotsHeader } from "./app-mounted-slots-header.js";
|
|
6
6
|
import { APP_RSC_RENDER_MODE_NAVIGATION, getRscRenderModeCacheVariant } from "./app-rsc-render-mode.js";
|
|
7
7
|
import { normalizeAppPageInterceptionProofPathname } from "./app-page-render-identity.js";
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { removeTrailingSlash } from "../utils/base-path.js";
|
|
2
|
-
import { checkHasConditions, requestContextFromRequest, safeRegExp } from "../config/config-matchers.js";
|
|
2
|
+
import { checkHasConditions, isSafeRegex, requestContextFromRequest, safeRegExp } from "../config/config-matchers.js";
|
|
3
3
|
//#region src/server/middleware-matcher.ts
|
|
4
4
|
const EMPTY_MIDDLEWARE_REQUEST_CONTEXT = {
|
|
5
5
|
headers: new Headers(),
|
|
@@ -7,6 +7,7 @@ const EMPTY_MIDDLEWARE_REQUEST_CONTEXT = {
|
|
|
7
7
|
query: new URLSearchParams(),
|
|
8
8
|
host: ""
|
|
9
9
|
};
|
|
10
|
+
const UNSAFE_MATCHER_PATTERN = Symbol("unsafe matcher pattern");
|
|
10
11
|
const _mwPatternCache = /* @__PURE__ */ new Map();
|
|
11
12
|
function matchesMiddleware(pathname, matcher, request, i18nConfig) {
|
|
12
13
|
if (!matcher) return true;
|
|
@@ -47,16 +48,19 @@ function stripLocalePrefix(pathname, i18nConfig) {
|
|
|
47
48
|
const segments = pathname.split("/");
|
|
48
49
|
const firstSegment = segments[1];
|
|
49
50
|
if (!firstSegment || !i18nConfig.locales.includes(firstSegment)) return null;
|
|
50
|
-
return
|
|
51
|
+
return "/" + segments.slice(2).join("/");
|
|
51
52
|
}
|
|
52
53
|
function matchPattern(pathname, pattern) {
|
|
53
|
-
|
|
54
|
+
const normalizedPattern = /[\\():*+?]/.test(pattern) ? pattern : removeTrailingSlash(pattern);
|
|
55
|
+
let cached = _mwPatternCache.get(normalizedPattern);
|
|
54
56
|
if (cached === void 0) {
|
|
55
|
-
cached = compileMatcherPattern(
|
|
56
|
-
_mwPatternCache.set(
|
|
57
|
+
cached = compileMatcherPattern(normalizedPattern);
|
|
58
|
+
_mwPatternCache.set(normalizedPattern, cached);
|
|
57
59
|
}
|
|
58
|
-
if (cached ===
|
|
59
|
-
return
|
|
60
|
+
if (cached === UNSAFE_MATCHER_PATTERN) return true;
|
|
61
|
+
if (cached === null) return removeTrailingSlash(pathname) === normalizedPattern;
|
|
62
|
+
if (cached.test(pathname)) return true;
|
|
63
|
+
return pathname.endsWith("/") && cached.test(removeTrailingSlash(pathname));
|
|
60
64
|
}
|
|
61
65
|
function extractConstraint(str, re) {
|
|
62
66
|
if (str[re.lastIndex] !== "(") return null;
|
|
@@ -74,7 +78,7 @@ function extractConstraint(str, re) {
|
|
|
74
78
|
}
|
|
75
79
|
function compileMatcherPattern(pattern) {
|
|
76
80
|
const hasConstraints = /:[\w-]+[*+]?\(/.test(pattern);
|
|
77
|
-
if (!hasConstraints && (pattern.includes("(") || pattern.includes("\\"))) return
|
|
81
|
+
if (!hasConstraints && (pattern.includes("(") || pattern.includes("\\"))) return compileMatcherRegExp("^" + pattern + "$", pattern);
|
|
78
82
|
let regexStr = "";
|
|
79
83
|
const tokenRe = /\/:([\w-]+)\*|\/:([\w-]+)\+|:([\w-]+)|[.]|[^/:.]+|./g;
|
|
80
84
|
let tok;
|
|
@@ -94,7 +98,14 @@ function compileMatcherPattern(pattern) {
|
|
|
94
98
|
else regexStr += group;
|
|
95
99
|
} else if (tok[0] === ".") regexStr += "\\.";
|
|
96
100
|
else regexStr += tok[0];
|
|
97
|
-
return
|
|
101
|
+
return compileMatcherRegExp("^" + regexStr + "$", pattern);
|
|
102
|
+
}
|
|
103
|
+
function compileMatcherRegExp(regexPattern, sourcePattern) {
|
|
104
|
+
if (!isSafeRegex(regexPattern)) {
|
|
105
|
+
console.warn(`[vinext] Rejecting potentially unsafe middleware matcher (ReDoS risk): ${sourcePattern}\n Middleware will run for all paths to avoid bypassing request guards.\n Simplify the matcher to avoid nested repetition.`);
|
|
106
|
+
return UNSAFE_MATCHER_PATTERN;
|
|
107
|
+
}
|
|
108
|
+
return safeRegExp(regexPattern);
|
|
98
109
|
}
|
|
99
110
|
//#endregion
|
|
100
111
|
export { matchPattern, matchesMiddleware };
|
|
@@ -35,10 +35,9 @@ type ExecuteMiddlewareOptions = {
|
|
|
35
35
|
i18nConfig?: NextI18nConfig | null;
|
|
36
36
|
includeErrorDetails?: boolean;
|
|
37
37
|
/**
|
|
38
|
-
* Whether the incoming request was a Next.js `_next/data`
|
|
39
|
-
*
|
|
40
|
-
*
|
|
41
|
-
* flag from the raw incoming headers and forward it explicitly.
|
|
38
|
+
* Whether the incoming request was recognized as a Next.js `_next/data`
|
|
39
|
+
* fetch. Internal headers are stripped before middleware runs, so adapters
|
|
40
|
+
* must derive and forward this from trusted URL normalization.
|
|
42
41
|
*/
|
|
43
42
|
isDataRequest?: boolean;
|
|
44
43
|
isProxy: boolean;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { normalizePathnameForRouteMatchStrict } from "../routing/utils.js";
|
|
2
|
-
import { addBasePathToPathname, hasBasePath, stripBasePath } from "../utils/base-path.js";
|
|
2
|
+
import { addBasePathToPathname, hasBasePath, removeTrailingSlash, stripBasePath } from "../utils/base-path.js";
|
|
3
3
|
import "./server-globals.js";
|
|
4
4
|
import { getRequestExecutionContext, runWithExecutionContext } from "../shims/request-context.js";
|
|
5
5
|
import { MIDDLEWARE_REWRITE_HEADER } from "./headers.js";
|
|
@@ -130,7 +130,7 @@ async function executeMiddleware(options) {
|
|
|
130
130
|
const matchPathname = options.basePath ? stripBasePath(normalizedPathname, options.basePath) : normalizedPathname;
|
|
131
131
|
if (!matchesMiddleware(matchPathname, middlewareMatcher(options.module), options.request, options.i18nConfig)) return { continue: true };
|
|
132
132
|
const nextRequest = createNextRequest(options.request, normalizedPathname, options.i18nConfig, options.basePath, options.trailingSlash, hadBasePath);
|
|
133
|
-
const fetchEvent = new NextFetchEvent({ page: matchPathname });
|
|
133
|
+
const fetchEvent = new NextFetchEvent({ page: removeTrailingSlash(matchPathname) });
|
|
134
134
|
let response;
|
|
135
135
|
try {
|
|
136
136
|
response = await middlewareFn(nextRequest, fetchEvent);
|
|
@@ -142,6 +142,8 @@ async function executeMiddleware(options) {
|
|
|
142
142
|
response: internalServerErrorResponse(options.includeErrorDetails ? "Middleware Error: " + (e instanceof Error ? e.message : String(e)) : "Internal Server Error"),
|
|
143
143
|
waitUntilPromises
|
|
144
144
|
};
|
|
145
|
+
} finally {
|
|
146
|
+
if (process.env.NODE_ENV !== "development" && nextRequest.body) nextRequest.body.cancel().catch(() => {});
|
|
145
147
|
}
|
|
146
148
|
const waitUntilPromises = drainFetchEvent(fetchEvent);
|
|
147
149
|
if (!response) return {
|
|
@@ -2,18 +2,9 @@ import { RouteManifest } from "../routing/app-route-graph.js";
|
|
|
2
2
|
import { CacheEntryReuseDecision, CacheEntryReuseProof } from "./cache-proof.js";
|
|
3
3
|
import { AppElementsSlotBinding } from "./app-elements-wire.js";
|
|
4
4
|
import { NavigationTrace, NavigationTraceFields } from "./navigation-trace.js";
|
|
5
|
+
import { OperationLane, OperationToken } from "./operation-token.js";
|
|
5
6
|
|
|
6
7
|
//#region src/server/navigation-planner.d.ts
|
|
7
|
-
type OperationLane = "hmr" | "navigation" | "prefetch" | "refresh" | "server-action" | "traverse";
|
|
8
|
-
type OperationToken = {
|
|
9
|
-
operationId: number;
|
|
10
|
-
lane: OperationLane;
|
|
11
|
-
baseVisibleCommitVersion: number;
|
|
12
|
-
graphVersion: string | null;
|
|
13
|
-
deploymentVersion: string | null;
|
|
14
|
-
targetSnapshotFingerprint: string;
|
|
15
|
-
cacheVariantFingerprint?: string;
|
|
16
|
-
};
|
|
17
8
|
type RouteSnapshot = {
|
|
18
9
|
interception: InterceptionSnapshot | null;
|
|
19
10
|
interceptionContext: string | null;
|
|
@@ -85,7 +76,7 @@ type CommitProposal = {
|
|
|
85
76
|
targetSnapshot: RouteSnapshot;
|
|
86
77
|
};
|
|
87
78
|
type NoCommitReason = "prefetchOnly";
|
|
88
|
-
type HardNavigationReason = "cacheProofRejected" | "interceptionProofRejected" | "rootBoundaryChanged";
|
|
79
|
+
type HardNavigationReason = "cacheProofRejected" | "cacheReuseTokenRejected" | "interceptionProofRejected" | "rootBoundaryChanged";
|
|
89
80
|
type RootBoundaryTransition = "currentRootBoundary" | "rootBoundaryChanged" | "rootBoundaryUnknown";
|
|
90
81
|
type NavigationDecision = {
|
|
91
82
|
kind: "requestWork";
|
|
@@ -314,4 +305,4 @@ declare const navigationPlanner: {
|
|
|
314
305
|
resolveSameLayoutAncestorPersistence: typeof resolveSameLayoutAncestorPersistence;
|
|
315
306
|
};
|
|
316
307
|
//#endregion
|
|
317
|
-
export { EarlyNavigationIntentDecision, EarlyNavigationIntentFacts, FlightResult, InterceptionSnapshot, MountedParallelSlotSnapshot, NavigationDecision, NavigationEvent, NavigationPlannerInput, NavigationPlannerState, NavigationReuseDecision, NavigationReuseFacts, OperationLane, OperationToken, ParallelSlotBindingSnapshot, RefreshScope, RootBoundaryTransition, RouteSnapshot, RscFetchResultDecision, RscFetchResultFacts, RscNavigationErrorDecision, RscNavigationErrorFacts, ServerActionResultDecision, ServerActionResultFacts, TraverseDirection, VisitedResponseCacheCandidateFacts, navigationPlanner, resolveDefaultOrUnmatchedSlotPersistenceForLayouts };
|
|
308
|
+
export { EarlyNavigationIntentDecision, EarlyNavigationIntentFacts, FlightResult, InterceptionSnapshot, MountedParallelSlotSnapshot, NavigationDecision, NavigationEvent, NavigationPlannerInput, NavigationPlannerState, NavigationReuseDecision, NavigationReuseFacts, type OperationLane, type OperationToken, ParallelSlotBindingSnapshot, RefreshScope, RootBoundaryTransition, RouteSnapshot, RscFetchResultDecision, RscFetchResultFacts, RscNavigationErrorDecision, RscNavigationErrorFacts, ServerActionResultDecision, ServerActionResultFacts, TraverseDirection, VisitedResponseCacheCandidateFacts, navigationPlanner, resolveDefaultOrUnmatchedSlotPersistenceForLayouts };
|
|
@@ -6,6 +6,7 @@ import "./app-elements.js";
|
|
|
6
6
|
import { resolveHardNavigationTargetFromRscResponse, resolveRscCompatibilityNavigationDecision } from "./app-rsc-cache-busting.js";
|
|
7
7
|
import { resolveRscRedirectLifecycleHop, resolveStreamedRscRedirectLifecycleHop } from "./app-browser-rsc-redirect.js";
|
|
8
8
|
import { NavigationTraceReasonCodes, createNavigationLifecycleTraceFields, createNavigationTrace } from "./navigation-trace.js";
|
|
9
|
+
import { verifyOperationTokenForCacheReuse } from "./operation-token.js";
|
|
9
10
|
//#region src/server/navigation-planner.ts
|
|
10
11
|
const ROUTE_INTERCEPTION_CONTEXT_SEPARATOR = "\0";
|
|
11
12
|
const CACHE_ENTRY_PROOF_MISSING_CODE = "CP_CACHE_ENTRY_PROOF_MISSING";
|
|
@@ -531,6 +532,18 @@ function createCacheProofRejectedDecision(options) {
|
|
|
531
532
|
url: options.event.result.href
|
|
532
533
|
};
|
|
533
534
|
}
|
|
535
|
+
function createCacheReuseTokenRejectedDecision(options) {
|
|
536
|
+
return {
|
|
537
|
+
kind: "hardNavigate",
|
|
538
|
+
reason: "cacheReuseTokenRejected",
|
|
539
|
+
token: options.event.token,
|
|
540
|
+
trace: createNavigationTrace(NavigationTraceReasonCodes.cacheReuseTokenRejected, {
|
|
541
|
+
...options.traceFields,
|
|
542
|
+
cacheReuseTokenReason: options.reason
|
|
543
|
+
}),
|
|
544
|
+
url: options.event.result.href
|
|
545
|
+
};
|
|
546
|
+
}
|
|
534
547
|
function createAcceptedCacheProofTraceFields(traceFields, decision) {
|
|
535
548
|
if (decision === null) return traceFields;
|
|
536
549
|
return {
|
|
@@ -619,6 +632,17 @@ function planFlightResponseArrived(options) {
|
|
|
619
632
|
traceFields
|
|
620
633
|
});
|
|
621
634
|
const acceptedCacheEntryDecision = cacheEntryProofEvaluation.decision;
|
|
635
|
+
if (acceptedCacheEntryDecision !== null) {
|
|
636
|
+
const reuseVerdict = verifyOperationTokenForCacheReuse(options.event.token, {
|
|
637
|
+
graphVersion: options.routeManifest?.graphVersion ?? null,
|
|
638
|
+
installedCacheVariantFingerprint: null
|
|
639
|
+
});
|
|
640
|
+
if (!reuseVerdict.authorized) return createCacheReuseTokenRejectedDecision({
|
|
641
|
+
event: options.event,
|
|
642
|
+
reason: reuseVerdict.reason,
|
|
643
|
+
traceFields
|
|
644
|
+
});
|
|
645
|
+
}
|
|
622
646
|
const commitTraceFields = createAcceptedCacheProofTraceFields(traceFields, acceptedCacheEntryDecision);
|
|
623
647
|
const cacheEntryProposalFields = createCacheEntryProposalFields(acceptedCacheEntryDecision);
|
|
624
648
|
if (targetSnapshot.interception !== null) {
|