@reckona/mreact-router 0.0.202 → 0.0.204
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 +58 -11
- package/dist/actions.d.ts +8 -3
- package/dist/actions.d.ts.map +1 -1
- package/dist/actions.js +125 -45
- package/dist/actions.js.map +1 -1
- package/dist/adapters/aws-lambda.d.ts +15 -4
- package/dist/adapters/aws-lambda.d.ts.map +1 -1
- package/dist/adapters/aws-lambda.js +301 -63
- package/dist/adapters/aws-lambda.js.map +1 -1
- package/dist/adapters/cloudflare.d.ts +5 -2
- package/dist/adapters/cloudflare.d.ts.map +1 -1
- package/dist/adapters/cloudflare.js +35 -11
- package/dist/adapters/cloudflare.js.map +1 -1
- package/dist/adapters/edge.d.ts +1 -1
- package/dist/adapters/edge.d.ts.map +1 -1
- package/dist/adapters/edge.js.map +1 -1
- package/dist/adapters/node.d.ts +5 -3
- package/dist/adapters/node.d.ts.map +1 -1
- package/dist/adapters/node.js +2 -1
- package/dist/adapters/node.js.map +1 -1
- package/dist/adapters/static.d.ts.map +1 -1
- package/dist/adapters/static.js +24 -2
- package/dist/adapters/static.js.map +1 -1
- package/dist/build.d.ts +4 -1
- package/dist/build.d.ts.map +1 -1
- package/dist/build.js +185 -13
- package/dist/build.js.map +1 -1
- package/dist/built-runtime.d.ts +2 -0
- package/dist/built-runtime.d.ts.map +1 -1
- package/dist/built-runtime.js +16 -0
- package/dist/built-runtime.js.map +1 -1
- package/dist/cache.d.ts +4 -2
- package/dist/cache.d.ts.map +1 -1
- package/dist/cache.js +216 -17
- package/dist/cache.js.map +1 -1
- package/dist/cli-options.d.ts +3 -0
- package/dist/cli-options.d.ts.map +1 -1
- package/dist/cli-options.js +10 -2
- package/dist/cli-options.js.map +1 -1
- package/dist/cli.js +14 -3
- package/dist/cli.js.map +1 -1
- package/dist/client.d.ts +5 -0
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +43 -3
- package/dist/client.js.map +1 -1
- package/dist/config.d.ts +3 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +21 -2
- package/dist/config.js.map +1 -1
- package/dist/csp.d.ts +1 -0
- package/dist/csp.d.ts.map +1 -1
- package/dist/csp.js +9 -0
- package/dist/csp.js.map +1 -1
- package/dist/dehydrate-policy.d.ts +3 -0
- package/dist/dehydrate-policy.d.ts.map +1 -0
- package/dist/dehydrate-policy.js +16 -0
- package/dist/dehydrate-policy.js.map +1 -0
- package/dist/dev-server.d.ts +7 -2
- package/dist/dev-server.d.ts.map +1 -1
- package/dist/dev-server.js +120 -11
- package/dist/dev-server.js.map +1 -1
- package/dist/http.d.ts +1 -1
- package/dist/http.d.ts.map +1 -1
- package/dist/http.js +147 -7
- package/dist/http.js.map +1 -1
- package/dist/index.d.ts +4 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/logger.d.ts +22 -1
- package/dist/logger.d.ts.map +1 -1
- package/dist/logger.js +22 -8
- package/dist/logger.js.map +1 -1
- package/dist/metadata.d.ts +8 -0
- package/dist/metadata.d.ts.map +1 -1
- package/dist/metadata.js +60 -22
- package/dist/metadata.js.map +1 -1
- package/dist/navigation-marker.d.ts +2 -0
- package/dist/navigation-marker.d.ts.map +1 -0
- package/dist/navigation-marker.js +357 -0
- package/dist/navigation-marker.js.map +1 -0
- package/dist/navigation-runtime.d.ts +1 -1
- package/dist/navigation-runtime.d.ts.map +1 -1
- package/dist/navigation-runtime.js +1 -1
- package/dist/navigation-runtime.js.map +1 -1
- package/dist/navigation.d.ts +1 -0
- package/dist/navigation.d.ts.map +1 -1
- package/dist/navigation.js +1 -1
- package/dist/navigation.js.map +1 -1
- package/dist/node-server.d.ts +5 -2
- package/dist/node-server.d.ts.map +1 -1
- package/dist/node-server.js +75 -6
- package/dist/node-server.js.map +1 -1
- package/dist/prerender-entry.d.ts +17 -1
- package/dist/prerender-entry.d.ts.map +1 -1
- package/dist/prerender-entry.js +98 -3
- package/dist/prerender-entry.js.map +1 -1
- package/dist/render.d.ts +16 -2
- package/dist/render.d.ts.map +1 -1
- package/dist/render.js +142 -135
- package/dist/render.js.map +1 -1
- package/dist/serve.d.ts +11 -2
- package/dist/serve.d.ts.map +1 -1
- package/dist/serve.js +67 -41
- package/dist/serve.js.map +1 -1
- package/dist/session.d.ts.map +1 -1
- package/dist/session.js +52 -19
- package/dist/session.js.map +1 -1
- package/dist/upgrade.d.ts +59 -2
- package/dist/upgrade.d.ts.map +1 -1
- package/dist/upgrade.js +282 -1
- package/dist/upgrade.js.map +1 -1
- package/dist/vite-config.d.ts +2 -0
- package/dist/vite-config.d.ts.map +1 -1
- package/dist/vite-config.js +2 -0
- package/dist/vite-config.js.map +1 -1
- package/dist/vite.d.ts +4 -1
- package/dist/vite.d.ts.map +1 -1
- package/dist/vite.js +117 -6
- package/dist/vite.js.map +1 -1
- package/package.json +11 -11
- package/src/actions.ts +146 -60
- package/src/adapters/aws-lambda.ts +426 -115
- package/src/adapters/cloudflare.ts +58 -12
- package/src/adapters/edge.ts +2 -0
- package/src/adapters/node.ts +8 -10
- package/src/adapters/static.ts +31 -2
- package/src/build.ts +217 -13
- package/src/built-runtime.ts +28 -0
- package/src/cache.ts +272 -19
- package/src/cli-options.ts +15 -2
- package/src/cli.ts +16 -2
- package/src/client.ts +47 -3
- package/src/config.ts +43 -6
- package/src/csp.ts +11 -0
- package/src/dehydrate-policy.ts +23 -0
- package/src/dev-server.ts +188 -15
- package/src/http.ts +172 -6
- package/src/index.ts +14 -1
- package/src/logger.ts +47 -10
- package/src/metadata.ts +125 -60
- package/src/navigation-marker.ts +423 -0
- package/src/navigation-runtime.ts +1 -0
- package/src/navigation.ts +1 -1
- package/src/node-server.ts +128 -22
- package/src/prerender-entry.ts +127 -5
- package/src/render.ts +859 -749
- package/src/serve.ts +112 -82
- package/src/session.ts +56 -25
- package/src/upgrade.ts +402 -3
- package/src/vite-config.ts +6 -0
- package/src/vite.ts +157 -5
package/src/serve.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { Server } from "node:http";
|
|
2
|
+
import type { DehydrateOptions } from "@reckona/mreact-query";
|
|
2
3
|
import { readFile } from "node:fs/promises";
|
|
3
4
|
import { join } from "node:path";
|
|
4
5
|
import type { BuiltPrerenderedRoute } from "./build.js";
|
|
@@ -22,10 +23,7 @@ import {
|
|
|
22
23
|
loadBuiltServerModuleArtifacts,
|
|
23
24
|
loadBuiltServerModuleArtifactsForRequest,
|
|
24
25
|
} from "./built-server-module-artifacts.js";
|
|
25
|
-
import {
|
|
26
|
-
type AppRoute,
|
|
27
|
-
type MatchedRoute,
|
|
28
|
-
} from "./routes.js";
|
|
26
|
+
import { type AppRoute, type MatchedRoute } from "./routes.js";
|
|
29
27
|
import type { AppRouterServerActionOptions } from "./actions.js";
|
|
30
28
|
import type { AppRouterImportPolicy } from "./import-policy.js";
|
|
31
29
|
import {
|
|
@@ -43,24 +41,21 @@ import {
|
|
|
43
41
|
traceContextFromRequest,
|
|
44
42
|
type RouterInstrumentation,
|
|
45
43
|
} from "./trace.js";
|
|
46
|
-
import {
|
|
47
|
-
|
|
48
|
-
htmlResponse,
|
|
49
|
-
} from "./http.js";
|
|
50
|
-
import {
|
|
51
|
-
emitRouterLog,
|
|
52
|
-
logDurationMs,
|
|
53
|
-
logNow,
|
|
54
|
-
type AppRouterLogger,
|
|
55
|
-
} from "./logger.js";
|
|
44
|
+
import { bytesResponse, htmlResponse } from "./http.js";
|
|
45
|
+
import { emitRouterLog, logDurationMs, logNow, type AppRouterLogger } from "./logger.js";
|
|
56
46
|
import { startNodeRequestServer } from "./node-server.js";
|
|
57
47
|
import { builtAppRuntimePreloadPlan } from "./preload-policy.js";
|
|
58
48
|
import { normalizeRoutePath } from "./route-path.js";
|
|
59
|
-
import type {
|
|
49
|
+
import type { HttpUpgradeOriginPolicy, ManagedHttpUpgradeHandler } from "./upgrade.js";
|
|
60
50
|
import {
|
|
61
51
|
isCurrentPrerenderedRoute,
|
|
62
52
|
isVisitorDependentResponse,
|
|
53
|
+
mergePrerenderedNavigationHtml,
|
|
54
|
+
protectNonceBearingResponse,
|
|
63
55
|
PRERENDERED_ROUTE_SCHEMA_VERSION,
|
|
56
|
+
replayedPrerenderedRouteHeaders,
|
|
57
|
+
storedPrerenderedRouteHeaders,
|
|
58
|
+
validatedPrerenderedNavigationHtml,
|
|
64
59
|
} from "./prerender-entry.js";
|
|
65
60
|
|
|
66
61
|
interface BuiltRuntimeCacheEntry {
|
|
@@ -99,6 +94,7 @@ let warnedImplicitHostTrust = false;
|
|
|
99
94
|
* Configures rendering a request against a built app-router output directory.
|
|
100
95
|
*/
|
|
101
96
|
export interface RenderBuiltAppRequestOptions {
|
|
97
|
+
dehydrateOptions?: DehydrateOptions | undefined;
|
|
102
98
|
outDir: string;
|
|
103
99
|
importPolicy?: AppRouterImportPolicy | undefined;
|
|
104
100
|
instrumentation?: RouterInstrumentation | undefined;
|
|
@@ -156,6 +152,7 @@ export interface BuiltAppRuntimePreloadStrategy {
|
|
|
156
152
|
* Configures the Node HTTP server used to serve a built app-router output.
|
|
157
153
|
*/
|
|
158
154
|
export interface StartServerOptions {
|
|
155
|
+
dehydrateOptions?: DehydrateOptions | undefined;
|
|
159
156
|
outDir: string;
|
|
160
157
|
port: number;
|
|
161
158
|
hostname?: string;
|
|
@@ -163,7 +160,11 @@ export interface StartServerOptions {
|
|
|
163
160
|
// a generic "Internal Server Error" body and logs the stack to stderr
|
|
164
161
|
// via console.error. Issue 071: stack traces must never end up in
|
|
165
162
|
// production responses.
|
|
166
|
-
errorHandler?: (error: unknown) => {
|
|
163
|
+
errorHandler?: (error: unknown) => {
|
|
164
|
+
body: string;
|
|
165
|
+
status: number;
|
|
166
|
+
headers?: Record<string, string>;
|
|
167
|
+
};
|
|
167
168
|
// When set, an incoming Host header that does not exactly match one of
|
|
168
169
|
// the listed values is replaced with the configured hostname/port for
|
|
169
170
|
// origin reconstruction. Use this in front of public deployments to
|
|
@@ -180,7 +181,13 @@ export interface StartServerOptions {
|
|
|
180
181
|
routeCache?: AppRouterCache | undefined;
|
|
181
182
|
serverActions?: AppRouterServerActionOptions | undefined;
|
|
182
183
|
sinkStrategy?: ResponseSinkStrategy;
|
|
183
|
-
onUpgrade?:
|
|
184
|
+
onUpgrade?: ManagedHttpUpgradeHandler | undefined;
|
|
185
|
+
/** Maximum time to wait for application-owned upgrade sockets during shutdown. */
|
|
186
|
+
upgradeCloseTimeoutMs?: number | undefined;
|
|
187
|
+
/** Maximum time an asynchronous upgrade handler may leave ownership undecided. */
|
|
188
|
+
upgradeDecisionTimeoutMs?: number | undefined;
|
|
189
|
+
/** Origin policy for application HTTP upgrades. Defaults to exact same-origin validation. */
|
|
190
|
+
upgradeOriginPolicy?: HttpUpgradeOriginPolicy | undefined;
|
|
184
191
|
/**
|
|
185
192
|
* Trusts the first `X-Forwarded-Proto` value when the socket is not TLS.
|
|
186
193
|
*
|
|
@@ -219,7 +226,7 @@ export function warnIfImplicitHostTrust(options: {
|
|
|
219
226
|
|
|
220
227
|
warnedImplicitHostTrust = true;
|
|
221
228
|
console.error(
|
|
222
|
-
|
|
229
|
+
'[mreact] Host header trust is implicit because neither allowedHosts nor hostPolicy is configured. Set allowedHosts for public deployments, hostPolicy: "strict" to reject unlisted Host headers, or hostPolicy: "trusted-proxy" when a trusted reverse proxy normalizes Host.',
|
|
223
230
|
);
|
|
224
231
|
}
|
|
225
232
|
|
|
@@ -255,6 +262,7 @@ export async function createBuiltRequestRuntime(
|
|
|
255
262
|
emitBuiltRequestStart(renderOptions.instrumentation, request);
|
|
256
263
|
const response = await renderBuiltAppRequestWithRuntime({
|
|
257
264
|
...renderOptions,
|
|
265
|
+
dehydrateOptions: renderOptions.dehydrateOptions ?? runtime.dehydrateOptions,
|
|
258
266
|
instrumentation: withoutBuiltRequestInstrumentation(renderOptions.instrumentation),
|
|
259
267
|
importPolicy:
|
|
260
268
|
renderOptions.importPolicy === undefined
|
|
@@ -400,8 +408,11 @@ function withoutBuiltRequestInstrumentation(
|
|
|
400
408
|
return undefined;
|
|
401
409
|
}
|
|
402
410
|
|
|
403
|
-
const {
|
|
404
|
-
|
|
411
|
+
const {
|
|
412
|
+
onRequestEnd: _onRequestEnd,
|
|
413
|
+
onRequestStart: _onRequestStart,
|
|
414
|
+
...remaining
|
|
415
|
+
} = instrumentation;
|
|
405
416
|
return remaining;
|
|
406
417
|
}
|
|
407
418
|
|
|
@@ -465,22 +476,15 @@ async function renderBuiltAppRequestWithRuntime(
|
|
|
465
476
|
// no middleware at all. Otherwise a middleware that gates access (auth,
|
|
466
477
|
// geo blocking, maintenance mode) would be bypassed for exactly the routes
|
|
467
478
|
// it is most often used to protect.
|
|
468
|
-
if (
|
|
469
|
-
!options.runtime.hasMiddleware &&
|
|
470
|
-
(request.method === "GET" || request.method === "HEAD")
|
|
471
|
-
) {
|
|
479
|
+
if (!options.runtime.hasMiddleware && (request.method === "GET" || request.method === "HEAD")) {
|
|
472
480
|
// Sync fast path when no external prerender store is configured (the
|
|
473
481
|
// common case): skip the Promise wrap that `readPrerenderedRoute`
|
|
474
482
|
// would otherwise introduce just to satisfy the async signature.
|
|
475
483
|
const prerendered =
|
|
476
484
|
options.prerenderStore === undefined
|
|
477
485
|
? options.runtime.prerenderedRoutes.get(normalizedPath)
|
|
478
|
-
: await readPrerenderedRoute(
|
|
479
|
-
|
|
480
|
-
normalizedPath,
|
|
481
|
-
options.prerenderStore,
|
|
482
|
-
);
|
|
483
|
-
const prerenderedResponse = prerenderedRouteResponse(prerendered, request.method);
|
|
486
|
+
: await readPrerenderedRoute(options.runtime, normalizedPath, options.prerenderStore);
|
|
487
|
+
const prerenderedResponse = prerenderedRouteResponse(prerendered, request);
|
|
484
488
|
|
|
485
489
|
if (prerenderedResponse !== undefined) {
|
|
486
490
|
return prerenderedResponse;
|
|
@@ -503,19 +507,12 @@ async function renderBuiltAppRequestWithRuntime(
|
|
|
503
507
|
// Middleware apps resolve prerendered HTML here instead, so that a
|
|
504
508
|
// middleware response or rewrite is honoured before the stored HTML is
|
|
505
509
|
// served. The lookup uses the post-middleware path.
|
|
506
|
-
if (
|
|
507
|
-
options.runtime.hasMiddleware &&
|
|
508
|
-
(request.method === "GET" || request.method === "HEAD")
|
|
509
|
-
) {
|
|
510
|
+
if (options.runtime.hasMiddleware && (request.method === "GET" || request.method === "HEAD")) {
|
|
510
511
|
const prerendered =
|
|
511
512
|
options.prerenderStore === undefined
|
|
512
513
|
? options.runtime.prerenderedRoutes.get(normalizedPath)
|
|
513
|
-
: await readPrerenderedRoute(
|
|
514
|
-
|
|
515
|
-
normalizedPath,
|
|
516
|
-
options.prerenderStore,
|
|
517
|
-
);
|
|
518
|
-
const prerenderedResponse = prerenderedRouteResponse(prerendered, request.method);
|
|
514
|
+
: await readPrerenderedRoute(options.runtime, normalizedPath, options.prerenderStore);
|
|
515
|
+
const prerenderedResponse = prerenderedRouteResponse(prerendered, request);
|
|
519
516
|
|
|
520
517
|
if (prerenderedResponse !== undefined) {
|
|
521
518
|
emitBuiltRenderTiming(options, request, timing, prerenderedResponse.status);
|
|
@@ -540,34 +537,34 @@ async function renderBuiltAppRequestWithRuntime(
|
|
|
540
537
|
requestUrl: new URL(request.url),
|
|
541
538
|
});
|
|
542
539
|
|
|
543
|
-
await applyBuiltPrerenderInvalidations(
|
|
544
|
-
options.runtime,
|
|
545
|
-
response,
|
|
546
|
-
options.prerenderStore,
|
|
547
|
-
);
|
|
540
|
+
await applyBuiltPrerenderInvalidations(options.runtime, response, options.prerenderStore);
|
|
548
541
|
|
|
549
|
-
return options.sinkStrategy === "buffer"
|
|
550
|
-
? await materializeResponseAsBuffer(response)
|
|
551
|
-
: response;
|
|
542
|
+
return options.sinkStrategy === "buffer" ? await materializeResponseAsBuffer(response) : response;
|
|
552
543
|
}
|
|
553
544
|
|
|
554
545
|
function prerenderedRouteResponse(
|
|
555
546
|
prerendered: BuiltPrerenderedRoute | undefined,
|
|
556
|
-
|
|
547
|
+
request: Request,
|
|
557
548
|
): Response | undefined {
|
|
558
549
|
if (!isCurrentPrerenderedRoute(prerendered)) {
|
|
559
550
|
return undefined;
|
|
560
551
|
}
|
|
561
552
|
|
|
562
|
-
|
|
553
|
+
const navigation = request.headers.get("x-mreact-navigation") === "1";
|
|
554
|
+
const html = navigation ? validatedPrerenderedNavigationHtml(prerendered) : prerendered.html;
|
|
555
|
+
if (html === undefined) {
|
|
556
|
+
return undefined;
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
if (request.method === "HEAD") {
|
|
563
560
|
return new Response(null, {
|
|
564
|
-
headers: prerendered
|
|
561
|
+
headers: replayedPrerenderedRouteHeaders(prerendered, request),
|
|
565
562
|
status: prerendered.status,
|
|
566
563
|
});
|
|
567
564
|
}
|
|
568
565
|
|
|
569
|
-
return htmlResponse(
|
|
570
|
-
headers: prerendered
|
|
566
|
+
return htmlResponse(html, {
|
|
567
|
+
headers: replayedPrerenderedRouteHeaders(prerendered, request),
|
|
571
568
|
status: prerendered.status,
|
|
572
569
|
});
|
|
573
570
|
}
|
|
@@ -662,7 +659,7 @@ async function applyBuiltAppResponseHook(
|
|
|
662
659
|
request: options.request,
|
|
663
660
|
});
|
|
664
661
|
|
|
665
|
-
return hooked instanceof Response ? hooked : response;
|
|
662
|
+
return protectNonceBearingResponse(hooked instanceof Response ? hooked : response);
|
|
666
663
|
}
|
|
667
664
|
|
|
668
665
|
async function materializeResponseAsBuffer(response: Response): Promise<Response> {
|
|
@@ -707,8 +704,12 @@ export async function startServer(
|
|
|
707
704
|
port: options.port,
|
|
708
705
|
resolveHost: resolveRequestHost,
|
|
709
706
|
trustForwardedProto: options.trustForwardedProto,
|
|
707
|
+
upgradeCloseTimeoutMs: options.upgradeCloseTimeoutMs,
|
|
708
|
+
upgradeDecisionTimeoutMs: options.upgradeDecisionTimeoutMs,
|
|
709
|
+
upgradeOriginPolicy: options.upgradeOriginPolicy,
|
|
710
710
|
render: (request) =>
|
|
711
711
|
runtime.render(request, {
|
|
712
|
+
dehydrateOptions: options.dehydrateOptions,
|
|
712
713
|
instrumentation: options.instrumentation,
|
|
713
714
|
logger: options.logger,
|
|
714
715
|
onResponse: options.onResponse,
|
|
@@ -927,6 +928,7 @@ function builtRenderAppRequestOptions(
|
|
|
927
928
|
clientScripts: options.runtime.clientScripts,
|
|
928
929
|
clientStylesByFile: options.runtime.clientStylesByFile,
|
|
929
930
|
clientStyles: options.runtime.clientStyles,
|
|
931
|
+
dehydrateOptions: options.dehydrateOptions,
|
|
930
932
|
importPolicy: {
|
|
931
933
|
...options.importPolicy,
|
|
932
934
|
allowedSourceDirs: options.runtime.allowedSourceDirs,
|
|
@@ -960,7 +962,9 @@ function builtRenderAppRequestOptions(
|
|
|
960
962
|
|
|
961
963
|
function mergeBuiltServerActionOptions(
|
|
962
964
|
options: AppRouterServerActionOptions | undefined,
|
|
963
|
-
allowedActions:
|
|
965
|
+
allowedActions:
|
|
966
|
+
| readonly { moduleId: string; exportName: string; inferred?: boolean }[]
|
|
967
|
+
| undefined,
|
|
964
968
|
): AppRouterServerActionOptions | undefined {
|
|
965
969
|
if (allowedActions === undefined) {
|
|
966
970
|
return options;
|
|
@@ -976,7 +980,9 @@ async function renderAndCachePrerenderWithLock(
|
|
|
976
980
|
options: RenderBuiltAppRequestOptions & { runtime: BuiltRuntime },
|
|
977
981
|
path: string,
|
|
978
982
|
): Promise<Response> {
|
|
979
|
-
const
|
|
983
|
+
const lockKey =
|
|
984
|
+
options.request.headers.get("x-mreact-navigation") === "1" ? `${path}\0navigation` : path;
|
|
985
|
+
const existing = options.runtime.prerenderLocks.get(lockKey);
|
|
980
986
|
|
|
981
987
|
if (existing !== undefined) {
|
|
982
988
|
const result = await existing;
|
|
@@ -988,12 +994,12 @@ async function renderAndCachePrerenderWithLock(
|
|
|
988
994
|
}
|
|
989
995
|
|
|
990
996
|
const task = runPrerenderRegeneration(options, path);
|
|
991
|
-
options.runtime.prerenderLocks.set(
|
|
997
|
+
options.runtime.prerenderLocks.set(lockKey, task);
|
|
992
998
|
|
|
993
999
|
try {
|
|
994
1000
|
return cloneResponse((await task).response);
|
|
995
1001
|
} finally {
|
|
996
|
-
options.runtime.prerenderLocks.delete(
|
|
1002
|
+
options.runtime.prerenderLocks.delete(lockKey);
|
|
997
1003
|
}
|
|
998
1004
|
}
|
|
999
1005
|
|
|
@@ -1001,15 +1007,15 @@ async function runPrerenderRegeneration(
|
|
|
1001
1007
|
options: RenderBuiltAppRequestOptions & { runtime: BuiltRuntime },
|
|
1002
1008
|
path: string,
|
|
1003
1009
|
): Promise<{ response: Response; shareable: boolean }> {
|
|
1010
|
+
const lockKey =
|
|
1011
|
+
options.request.headers.get("x-mreact-navigation") === "1" ? `${path}\0navigation` : path;
|
|
1004
1012
|
const regenerate = async () => {
|
|
1005
1013
|
const stored = await readPrerenderedRoute(options.runtime, path, options.prerenderStore);
|
|
1014
|
+
const storedResponse = prerenderedRouteResponse(stored, options.request);
|
|
1006
1015
|
|
|
1007
|
-
if (
|
|
1016
|
+
if (storedResponse !== undefined) {
|
|
1008
1017
|
return {
|
|
1009
|
-
response:
|
|
1010
|
-
headers: stored.headers,
|
|
1011
|
-
status: stored.status,
|
|
1012
|
-
}),
|
|
1018
|
+
response: storedResponse,
|
|
1013
1019
|
shareable: true,
|
|
1014
1020
|
};
|
|
1015
1021
|
}
|
|
@@ -1018,7 +1024,10 @@ async function runPrerenderRegeneration(
|
|
|
1018
1024
|
// render that depended on this visitor's request headers must not become
|
|
1019
1025
|
// one; it is returned to the caller without being stored. The signal starts
|
|
1020
1026
|
// closed so that a render which never reports back is not stored either.
|
|
1021
|
-
const renderSignals = {
|
|
1027
|
+
const renderSignals = {
|
|
1028
|
+
headerDependent: () => true,
|
|
1029
|
+
strictTransportSecurity: () => undefined as string | undefined,
|
|
1030
|
+
};
|
|
1022
1031
|
const response = await renderBuiltDynamicResponse({ ...options, renderSignals });
|
|
1023
1032
|
|
|
1024
1033
|
if (!response.ok) {
|
|
@@ -1039,21 +1048,34 @@ async function runPrerenderRegeneration(
|
|
|
1039
1048
|
};
|
|
1040
1049
|
}
|
|
1041
1050
|
|
|
1051
|
+
const navigation = options.request.headers.get("x-mreact-navigation") === "1";
|
|
1052
|
+
if (navigation && stored === undefined) {
|
|
1053
|
+
return {
|
|
1054
|
+
response: htmlResponse(body, {
|
|
1055
|
+
headers: response.headers,
|
|
1056
|
+
status: response.status,
|
|
1057
|
+
}),
|
|
1058
|
+
shareable: false,
|
|
1059
|
+
};
|
|
1060
|
+
}
|
|
1061
|
+
|
|
1042
1062
|
return {
|
|
1043
1063
|
response: await cacheRegeneratedPrerenderedRoute(
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1064
|
+
options.runtime,
|
|
1065
|
+
path,
|
|
1066
|
+
body,
|
|
1067
|
+
response,
|
|
1068
|
+
renderSignals.strictTransportSecurity(),
|
|
1069
|
+
options.prerenderStore,
|
|
1070
|
+
navigation ? stored : undefined,
|
|
1071
|
+
),
|
|
1050
1072
|
shareable: true,
|
|
1051
1073
|
};
|
|
1052
1074
|
};
|
|
1053
1075
|
|
|
1054
1076
|
return options.prerenderStore?.withLock === undefined
|
|
1055
1077
|
? await regenerate()
|
|
1056
|
-
: await options.prerenderStore.withLock(
|
|
1078
|
+
: await options.prerenderStore.withLock(lockKey, regenerate);
|
|
1057
1079
|
}
|
|
1058
1080
|
|
|
1059
1081
|
async function applyBuiltPrerenderInvalidations(
|
|
@@ -1079,19 +1101,27 @@ async function cacheRegeneratedPrerenderedRoute(
|
|
|
1079
1101
|
path: string,
|
|
1080
1102
|
body: string,
|
|
1081
1103
|
response: Response,
|
|
1104
|
+
strictTransportSecurity: string | undefined,
|
|
1082
1105
|
store: AppRouterPrerenderStore | undefined,
|
|
1106
|
+
existing?: BuiltPrerenderedRoute | undefined,
|
|
1083
1107
|
): Promise<Response> {
|
|
1084
|
-
const headers
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1108
|
+
const headers = storedPrerenderedRouteHeaders(response.headers);
|
|
1109
|
+
const entry: BuiltPrerenderedRoute =
|
|
1110
|
+
existing === undefined
|
|
1111
|
+
? {
|
|
1112
|
+
headers,
|
|
1113
|
+
html: body,
|
|
1114
|
+
schemaVersion: PRERENDERED_ROUTE_SCHEMA_VERSION,
|
|
1115
|
+
status: response.status,
|
|
1116
|
+
...(strictTransportSecurity === undefined ? {} : { strictTransportSecurity }),
|
|
1117
|
+
}
|
|
1118
|
+
: mergePrerenderedNavigationHtml(existing, body);
|
|
1119
|
+
if (entry === existing) {
|
|
1120
|
+
return htmlResponse(body, {
|
|
1121
|
+
headers: response.headers,
|
|
1122
|
+
status: response.status,
|
|
1123
|
+
});
|
|
1124
|
+
}
|
|
1095
1125
|
runtime.prerenderedRoutes.set(path, entry);
|
|
1096
1126
|
await store?.set(path, entry);
|
|
1097
1127
|
|
package/src/session.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { deleteCookie, parseCookieHeader, setCookie } from "./cookies.js";
|
|
1
|
+
import { deleteCookie, parseCookieHeader, serializeCookie, setCookie } from "./cookies.js";
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* Stores session data and expiration metadata for one session id.
|
|
@@ -43,32 +43,54 @@ const DEFAULT_MAX_AGE_SECONDS = 7 * 24 * 60 * 60;
|
|
|
43
43
|
const SESSION_COOKIE_DEV = "mreact.session";
|
|
44
44
|
const SESSION_COOKIE_PROD = "__Host-mreact.session";
|
|
45
45
|
|
|
46
|
-
function
|
|
47
|
-
|
|
46
|
+
function usesHardenedCookieDefaults(): boolean {
|
|
47
|
+
const nodeEnv = typeof process === "undefined" ? undefined : process["env"]?.["NODE_ENV"];
|
|
48
|
+
return nodeEnv !== "development" && nodeEnv !== "test";
|
|
48
49
|
}
|
|
49
50
|
|
|
50
|
-
function
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
function sessionCookieOptions(options: SessionCookieOptions = {}) {
|
|
55
|
-
const production = isProduction();
|
|
56
|
-
|
|
57
|
-
return {
|
|
51
|
+
function resolveSessionCookie(options: SessionCookieOptions = {}) {
|
|
52
|
+
const hardened = usesHardenedCookieDefaults();
|
|
53
|
+
const cookieOptions = {
|
|
58
54
|
httpOnly: true,
|
|
59
55
|
maxAge: options.maxAgeSeconds ?? DEFAULT_MAX_AGE_SECONDS,
|
|
60
|
-
path:
|
|
56
|
+
path: options.path ?? "/",
|
|
61
57
|
sameSite: options.sameSite ?? "Lax",
|
|
62
|
-
secure: options.secure ??
|
|
58
|
+
secure: options.secure ?? hardened,
|
|
63
59
|
} as const;
|
|
60
|
+
const canUseHostPrefix = cookieOptions.secure && cookieOptions.path === "/";
|
|
61
|
+
|
|
62
|
+
return {
|
|
63
|
+
name:
|
|
64
|
+
options.cookieName ??
|
|
65
|
+
(hardened && canUseHostPrefix ? SESSION_COOKIE_PROD : SESSION_COOKIE_DEV),
|
|
66
|
+
options: cookieOptions,
|
|
67
|
+
};
|
|
64
68
|
}
|
|
65
69
|
|
|
66
|
-
function
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
+
function validateResolvedSessionCookie(cookie: ReturnType<typeof resolveSessionCookie>): void {
|
|
71
|
+
serializeCookie(cookie.name, "", cookie.options);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function assertMutableResponseHeaders(response: Response): void {
|
|
75
|
+
const probeName = "x-mreact-session-header-probe";
|
|
76
|
+
const previous = response.headers.get(probeName);
|
|
77
|
+
|
|
78
|
+
try {
|
|
79
|
+
response.headers.set(probeName, "1");
|
|
80
|
+
if (previous === null) {
|
|
81
|
+
response.headers.delete(probeName);
|
|
82
|
+
} else {
|
|
83
|
+
response.headers.set(probeName, previous);
|
|
84
|
+
}
|
|
85
|
+
} catch (cause) {
|
|
86
|
+
throw new TypeError("Session helpers require a Response with mutable headers.", { cause });
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function readSessionId(request: Request, options: SessionCookieOptions = {}): string | undefined {
|
|
91
|
+
const cookie = resolveSessionCookie(options);
|
|
70
92
|
const values = parseCookieHeader(request.headers.get("cookie"));
|
|
71
|
-
return values.get(
|
|
93
|
+
return values.get(cookie.name);
|
|
72
94
|
}
|
|
73
95
|
|
|
74
96
|
function createSessionId(): string {
|
|
@@ -208,6 +230,9 @@ export async function createSession<TData>(
|
|
|
208
230
|
): Promise<SessionRecord<TData>> {
|
|
209
231
|
const now = Date.now();
|
|
210
232
|
const maxAgeSeconds = options.maxAgeSeconds ?? DEFAULT_MAX_AGE_SECONDS;
|
|
233
|
+
const cookie = resolveSessionCookie({ ...options, maxAgeSeconds });
|
|
234
|
+
validateResolvedSessionCookie(cookie);
|
|
235
|
+
assertMutableResponseHeaders(response);
|
|
211
236
|
const record: SessionRecord<TData> = {
|
|
212
237
|
createdAt: now,
|
|
213
238
|
data,
|
|
@@ -216,9 +241,7 @@ export async function createSession<TData>(
|
|
|
216
241
|
};
|
|
217
242
|
|
|
218
243
|
await store.set(record);
|
|
219
|
-
setCookie(response,
|
|
220
|
-
...sessionCookieOptions({ ...options, maxAgeSeconds }),
|
|
221
|
-
});
|
|
244
|
+
setCookie(response, cookie.name, record.id, cookie.options);
|
|
222
245
|
|
|
223
246
|
return record;
|
|
224
247
|
}
|
|
@@ -232,16 +255,22 @@ export async function destroySession<TData>(
|
|
|
232
255
|
store: SessionStore<TData>,
|
|
233
256
|
options: SessionCookieOptions = {},
|
|
234
257
|
): Promise<void> {
|
|
258
|
+
const cookie = resolveSessionCookie(options);
|
|
259
|
+
validateResolvedSessionCookie({
|
|
260
|
+
name: cookie.name,
|
|
261
|
+
options: { ...cookie.options, maxAge: 0 },
|
|
262
|
+
});
|
|
263
|
+
assertMutableResponseHeaders(response);
|
|
235
264
|
const id = readSessionId(request, options);
|
|
236
265
|
|
|
237
266
|
if (id !== undefined) {
|
|
238
267
|
await store.delete(id);
|
|
239
268
|
}
|
|
240
269
|
|
|
241
|
-
deleteCookie(response,
|
|
242
|
-
path:
|
|
243
|
-
sameSite: options.sameSite
|
|
244
|
-
secure: options.secure
|
|
270
|
+
deleteCookie(response, cookie.name, {
|
|
271
|
+
path: cookie.options.path,
|
|
272
|
+
sameSite: cookie.options.sameSite,
|
|
273
|
+
secure: cookie.options.secure,
|
|
245
274
|
});
|
|
246
275
|
}
|
|
247
276
|
|
|
@@ -256,6 +285,8 @@ export async function rotateSession<TData>(
|
|
|
256
285
|
store: SessionStore<TData>,
|
|
257
286
|
options: SessionCookieOptions = {},
|
|
258
287
|
): Promise<SessionRecord<TData> | undefined> {
|
|
288
|
+
validateResolvedSessionCookie(resolveSessionCookie(options));
|
|
289
|
+
assertMutableResponseHeaders(response);
|
|
259
290
|
const current = await getSession(request, store, options);
|
|
260
291
|
|
|
261
292
|
if (current === undefined) {
|