fix: vite
This commit is contained in:
580
node_modules/@sveltejs/kit/src/runtime/server/respond.js
generated
vendored
Normal file
580
node_modules/@sveltejs/kit/src/runtime/server/respond.js
generated
vendored
Normal file
@@ -0,0 +1,580 @@
|
||||
import { DEV } from 'esm-env';
|
||||
import { base, app_dir } from '__sveltekit/paths';
|
||||
import { is_endpoint_request, render_endpoint } from './endpoint.js';
|
||||
import { render_page } from './page/index.js';
|
||||
import { render_response } from './page/render.js';
|
||||
import { respond_with_error } from './page/respond_with_error.js';
|
||||
import { is_form_content_type } from '../../utils/http.js';
|
||||
import { handle_fatal_error, method_not_allowed, redirect_response } from './utils.js';
|
||||
import { decode_pathname, decode_params, disable_search, normalize_path } from '../../utils/url.js';
|
||||
import { exec } from '../../utils/routing.js';
|
||||
import { redirect_json_response, render_data } from './data/index.js';
|
||||
import { add_cookies_to_headers, get_cookies } from './cookie.js';
|
||||
import { create_fetch } from './fetch.js';
|
||||
import { HttpError, Redirect, SvelteKitError } from '../control.js';
|
||||
import {
|
||||
validate_layout_exports,
|
||||
validate_layout_server_exports,
|
||||
validate_page_exports,
|
||||
validate_page_server_exports,
|
||||
validate_server_exports
|
||||
} from '../../utils/exports.js';
|
||||
import { get_option } from '../../utils/options.js';
|
||||
import { json, text } from '../../exports/index.js';
|
||||
import { action_json_redirect, is_action_json_request } from './page/actions.js';
|
||||
import { INVALIDATED_PARAM, TRAILING_SLASH_PARAM } from '../shared.js';
|
||||
import { get_public_env } from './env_module.js';
|
||||
import { load_page_nodes } from './page/load_page_nodes.js';
|
||||
import { get_page_config } from '../../utils/route_config.js';
|
||||
import { resolve_route } from './page/server_routing.js';
|
||||
import { validateHeaders } from './validate-headers.js';
|
||||
import {
|
||||
has_data_suffix,
|
||||
has_resolution_suffix,
|
||||
strip_data_suffix,
|
||||
strip_resolution_suffix
|
||||
} from '../pathname.js';
|
||||
|
||||
/* global __SVELTEKIT_ADAPTER_NAME__ */
|
||||
/* global __SVELTEKIT_DEV__ */
|
||||
|
||||
/** @type {import('types').RequiredResolveOptions['transformPageChunk']} */
|
||||
const default_transform = ({ html }) => html;
|
||||
|
||||
/** @type {import('types').RequiredResolveOptions['filterSerializedResponseHeaders']} */
|
||||
const default_filter = () => false;
|
||||
|
||||
/** @type {import('types').RequiredResolveOptions['preload']} */
|
||||
const default_preload = ({ type }) => type === 'js' || type === 'css';
|
||||
|
||||
const page_methods = new Set(['GET', 'HEAD', 'POST']);
|
||||
|
||||
const allowed_page_methods = new Set(['GET', 'HEAD', 'OPTIONS']);
|
||||
|
||||
/**
|
||||
* @param {Request} request
|
||||
* @param {import('types').SSROptions} options
|
||||
* @param {import('@sveltejs/kit').SSRManifest} manifest
|
||||
* @param {import('types').SSRState} state
|
||||
* @returns {Promise<Response>}
|
||||
*/
|
||||
export async function respond(request, options, manifest, state) {
|
||||
/** URL but stripped from the potential `/__data.json` suffix and its search param */
|
||||
const url = new URL(request.url);
|
||||
|
||||
if (options.csrf_check_origin) {
|
||||
const forbidden =
|
||||
is_form_content_type(request) &&
|
||||
(request.method === 'POST' ||
|
||||
request.method === 'PUT' ||
|
||||
request.method === 'PATCH' ||
|
||||
request.method === 'DELETE') &&
|
||||
request.headers.get('origin') !== url.origin;
|
||||
|
||||
if (forbidden) {
|
||||
const csrf_error = new HttpError(
|
||||
403,
|
||||
`Cross-site ${request.method} form submissions are forbidden`
|
||||
);
|
||||
if (request.headers.get('accept') === 'application/json') {
|
||||
return json(csrf_error.body, { status: csrf_error.status });
|
||||
}
|
||||
return text(csrf_error.body.message, { status: csrf_error.status });
|
||||
}
|
||||
}
|
||||
|
||||
if (options.hash_routing && url.pathname !== base + '/' && url.pathname !== '/[fallback]') {
|
||||
return text('Not found', { status: 404 });
|
||||
}
|
||||
|
||||
/** @type {boolean[] | undefined} */
|
||||
let invalidated_data_nodes;
|
||||
|
||||
/**
|
||||
* If the request is for a route resolution, first modify the URL, then continue as normal
|
||||
* for path resolution, then return the route object as a JS file.
|
||||
*/
|
||||
const is_route_resolution_request = has_resolution_suffix(url.pathname);
|
||||
const is_data_request = has_data_suffix(url.pathname);
|
||||
|
||||
if (is_route_resolution_request) {
|
||||
url.pathname = strip_resolution_suffix(url.pathname);
|
||||
} else if (is_data_request) {
|
||||
url.pathname =
|
||||
strip_data_suffix(url.pathname) +
|
||||
(url.searchParams.get(TRAILING_SLASH_PARAM) === '1' ? '/' : '') || '/';
|
||||
url.searchParams.delete(TRAILING_SLASH_PARAM);
|
||||
invalidated_data_nodes = url.searchParams
|
||||
.get(INVALIDATED_PARAM)
|
||||
?.split('')
|
||||
.map((node) => node === '1');
|
||||
url.searchParams.delete(INVALIDATED_PARAM);
|
||||
}
|
||||
|
||||
let resolved_path;
|
||||
|
||||
try {
|
||||
// reroute could alter the given URL, so we pass a copy
|
||||
resolved_path = options.hooks.reroute({ url: new URL(url) }) ?? url.pathname;
|
||||
} catch {
|
||||
return text('Internal Server Error', {
|
||||
status: 500
|
||||
});
|
||||
}
|
||||
|
||||
try {
|
||||
resolved_path = decode_pathname(resolved_path);
|
||||
} catch {
|
||||
return text('Malformed URI', { status: 400 });
|
||||
}
|
||||
|
||||
/** @type {import('types').SSRRoute | null} */
|
||||
let route = null;
|
||||
|
||||
/** @type {Record<string, string>} */
|
||||
let params = {};
|
||||
|
||||
if (base && !state.prerendering?.fallback) {
|
||||
if (!resolved_path.startsWith(base)) {
|
||||
return text('Not found', { status: 404 });
|
||||
}
|
||||
resolved_path = resolved_path.slice(base.length) || '/';
|
||||
}
|
||||
|
||||
if (is_route_resolution_request) {
|
||||
return resolve_route(resolved_path, new URL(request.url), manifest);
|
||||
}
|
||||
|
||||
if (resolved_path === `/${app_dir}/env.js`) {
|
||||
return get_public_env(request);
|
||||
}
|
||||
|
||||
if (resolved_path.startsWith(`/${app_dir}`)) {
|
||||
// Ensure that 404'd static assets are not cached - some adapters might apply caching by default
|
||||
const headers = new Headers();
|
||||
headers.set('cache-control', 'public, max-age=0, must-revalidate');
|
||||
return text('Not found', { status: 404, headers });
|
||||
}
|
||||
|
||||
if (!state.prerendering?.fallback) {
|
||||
// TODO this could theoretically break — should probably be inside a try-catch
|
||||
const matchers = await manifest._.matchers();
|
||||
|
||||
for (const candidate of manifest._.routes) {
|
||||
const match = candidate.pattern.exec(resolved_path);
|
||||
if (!match) continue;
|
||||
|
||||
const matched = exec(match, candidate.params, matchers);
|
||||
if (matched) {
|
||||
route = candidate;
|
||||
params = decode_params(matched);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** @type {import('types').TrailingSlash | void} */
|
||||
let trailing_slash = undefined;
|
||||
|
||||
/** @type {Record<string, string>} */
|
||||
const headers = {};
|
||||
|
||||
/** @type {Record<string, import('./page/types.js').Cookie>} */
|
||||
let cookies_to_add = {};
|
||||
|
||||
/** @type {import('@sveltejs/kit').RequestEvent} */
|
||||
const event = {
|
||||
// @ts-expect-error `cookies` and `fetch` need to be created after the `event` itself
|
||||
cookies: null,
|
||||
// @ts-expect-error
|
||||
fetch: null,
|
||||
getClientAddress:
|
||||
state.getClientAddress ||
|
||||
(() => {
|
||||
throw new Error(
|
||||
`${__SVELTEKIT_ADAPTER_NAME__} does not specify getClientAddress. Please raise an issue`
|
||||
);
|
||||
}),
|
||||
locals: {},
|
||||
params,
|
||||
platform: state.platform,
|
||||
request,
|
||||
route: { id: route?.id ?? null },
|
||||
setHeaders: (new_headers) => {
|
||||
if (__SVELTEKIT_DEV__) {
|
||||
validateHeaders(new_headers);
|
||||
}
|
||||
|
||||
for (const key in new_headers) {
|
||||
const lower = key.toLowerCase();
|
||||
const value = new_headers[key];
|
||||
|
||||
if (lower === 'set-cookie') {
|
||||
throw new Error(
|
||||
'Use `event.cookies.set(name, value, options)` instead of `event.setHeaders` to set cookies'
|
||||
);
|
||||
} else if (lower in headers) {
|
||||
throw new Error(`"${key}" header is already set`);
|
||||
} else {
|
||||
headers[lower] = value;
|
||||
|
||||
if (state.prerendering && lower === 'cache-control') {
|
||||
state.prerendering.cache = /** @type {string} */ (value);
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
url,
|
||||
isDataRequest: is_data_request,
|
||||
isSubRequest: state.depth > 0
|
||||
};
|
||||
|
||||
/** @type {import('types').RequiredResolveOptions} */
|
||||
let resolve_opts = {
|
||||
transformPageChunk: default_transform,
|
||||
filterSerializedResponseHeaders: default_filter,
|
||||
preload: default_preload
|
||||
};
|
||||
|
||||
try {
|
||||
// determine whether we need to redirect to add/remove a trailing slash
|
||||
if (route) {
|
||||
// if `paths.base === '/a/b/c`, then the root route is `/a/b/c/`,
|
||||
// regardless of the `trailingSlash` route option
|
||||
if (url.pathname === base || url.pathname === base + '/') {
|
||||
trailing_slash = 'always';
|
||||
} else if (route.page) {
|
||||
const nodes = await load_page_nodes(route.page, manifest);
|
||||
|
||||
if (DEV) {
|
||||
const layouts = nodes.slice(0, -1);
|
||||
const page = nodes.at(-1);
|
||||
|
||||
for (const layout of layouts) {
|
||||
if (layout) {
|
||||
validate_layout_server_exports(
|
||||
layout.server,
|
||||
/** @type {string} */ (layout.server_id)
|
||||
);
|
||||
validate_layout_exports(
|
||||
layout.universal,
|
||||
/** @type {string} */ (layout.universal_id)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (page) {
|
||||
validate_page_server_exports(page.server, /** @type {string} */ (page.server_id));
|
||||
validate_page_exports(page.universal, /** @type {string} */ (page.universal_id));
|
||||
}
|
||||
}
|
||||
|
||||
trailing_slash = get_option(nodes, 'trailingSlash');
|
||||
} else if (route.endpoint) {
|
||||
const node = await route.endpoint();
|
||||
trailing_slash = node.trailingSlash;
|
||||
|
||||
if (DEV) {
|
||||
validate_server_exports(node, /** @type {string} */ (route.endpoint_id));
|
||||
}
|
||||
}
|
||||
|
||||
if (!is_data_request) {
|
||||
const normalized = normalize_path(url.pathname, trailing_slash ?? 'never');
|
||||
|
||||
if (normalized !== url.pathname && !state.prerendering?.fallback) {
|
||||
return new Response(undefined, {
|
||||
status: 308,
|
||||
headers: {
|
||||
'x-sveltekit-normalize': '1',
|
||||
location:
|
||||
// ensure paths starting with '//' are not treated as protocol-relative
|
||||
(normalized.startsWith('//') ? url.origin + normalized : normalized) +
|
||||
(url.search === '?' ? '' : url.search)
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (state.before_handle || state.emulator?.platform) {
|
||||
let config = {};
|
||||
|
||||
/** @type {import('types').PrerenderOption} */
|
||||
let prerender = false;
|
||||
|
||||
if (route.endpoint) {
|
||||
const node = await route.endpoint();
|
||||
config = node.config ?? config;
|
||||
prerender = node.prerender ?? prerender;
|
||||
} else if (route.page) {
|
||||
const nodes = await load_page_nodes(route.page, manifest);
|
||||
config = get_page_config(nodes) ?? config;
|
||||
prerender = get_option(nodes, 'prerender') ?? false;
|
||||
}
|
||||
|
||||
if (state.before_handle) {
|
||||
state.before_handle(event, config, prerender);
|
||||
}
|
||||
|
||||
if (state.emulator?.platform) {
|
||||
event.platform = await state.emulator.platform({ config, prerender });
|
||||
}
|
||||
}
|
||||
} else if (state.emulator?.platform) {
|
||||
event.platform = await state.emulator.platform({
|
||||
config: {},
|
||||
prerender: !!state.prerendering?.fallback
|
||||
});
|
||||
}
|
||||
|
||||
const { cookies, new_cookies, get_cookie_header, set_internal } = get_cookies(
|
||||
request,
|
||||
url,
|
||||
trailing_slash ?? 'never'
|
||||
);
|
||||
|
||||
cookies_to_add = new_cookies;
|
||||
event.cookies = cookies;
|
||||
event.fetch = create_fetch({
|
||||
event,
|
||||
options,
|
||||
manifest,
|
||||
state,
|
||||
get_cookie_header,
|
||||
set_internal
|
||||
});
|
||||
|
||||
if (state.prerendering && !state.prerendering.fallback) disable_search(url);
|
||||
|
||||
const response = await options.hooks.handle({
|
||||
event,
|
||||
resolve: (event, opts) =>
|
||||
resolve(event, opts).then((response) => {
|
||||
// add headers/cookies here, rather than inside `resolve`, so that we
|
||||
// can do it once for all responses instead of once per `return`
|
||||
for (const key in headers) {
|
||||
const value = headers[key];
|
||||
response.headers.set(key, /** @type {string} */ (value));
|
||||
}
|
||||
|
||||
add_cookies_to_headers(response.headers, Object.values(cookies_to_add));
|
||||
|
||||
if (state.prerendering && event.route.id !== null) {
|
||||
response.headers.set('x-sveltekit-routeid', encodeURI(event.route.id));
|
||||
}
|
||||
|
||||
return response;
|
||||
})
|
||||
});
|
||||
|
||||
// respond with 304 if etag matches
|
||||
if (response.status === 200 && response.headers.has('etag')) {
|
||||
let if_none_match_value = request.headers.get('if-none-match');
|
||||
|
||||
// ignore W/ prefix https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/If-None-Match#directives
|
||||
if (if_none_match_value?.startsWith('W/"')) {
|
||||
if_none_match_value = if_none_match_value.substring(2);
|
||||
}
|
||||
|
||||
const etag = /** @type {string} */ (response.headers.get('etag'));
|
||||
|
||||
if (if_none_match_value === etag) {
|
||||
const headers = new Headers({ etag });
|
||||
|
||||
// https://datatracker.ietf.org/doc/html/rfc7232#section-4.1 + set-cookie
|
||||
for (const key of [
|
||||
'cache-control',
|
||||
'content-location',
|
||||
'date',
|
||||
'expires',
|
||||
'vary',
|
||||
'set-cookie'
|
||||
]) {
|
||||
const value = response.headers.get(key);
|
||||
if (value) headers.set(key, value);
|
||||
}
|
||||
|
||||
return new Response(undefined, {
|
||||
status: 304,
|
||||
headers
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Edge case: If user does `return Response(30x)` in handle hook while processing a data request,
|
||||
// we need to transform the redirect response to a corresponding JSON response.
|
||||
if (is_data_request && response.status >= 300 && response.status <= 308) {
|
||||
const location = response.headers.get('location');
|
||||
if (location) {
|
||||
return redirect_json_response(new Redirect(/** @type {any} */ (response.status), location));
|
||||
}
|
||||
}
|
||||
|
||||
return response;
|
||||
} catch (e) {
|
||||
if (e instanceof Redirect) {
|
||||
const response = is_data_request
|
||||
? redirect_json_response(e)
|
||||
: route?.page && is_action_json_request(event)
|
||||
? action_json_redirect(e)
|
||||
: redirect_response(e.status, e.location);
|
||||
add_cookies_to_headers(response.headers, Object.values(cookies_to_add));
|
||||
return response;
|
||||
}
|
||||
return await handle_fatal_error(event, options, e);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {import('@sveltejs/kit').RequestEvent} event
|
||||
* @param {import('@sveltejs/kit').ResolveOptions} [opts]
|
||||
*/
|
||||
async function resolve(event, opts) {
|
||||
try {
|
||||
if (opts) {
|
||||
resolve_opts = {
|
||||
transformPageChunk: opts.transformPageChunk || default_transform,
|
||||
filterSerializedResponseHeaders: opts.filterSerializedResponseHeaders || default_filter,
|
||||
preload: opts.preload || default_preload
|
||||
};
|
||||
}
|
||||
|
||||
if (options.hash_routing || state.prerendering?.fallback) {
|
||||
return await render_response({
|
||||
event,
|
||||
options,
|
||||
manifest,
|
||||
state,
|
||||
page_config: { ssr: false, csr: true },
|
||||
status: 200,
|
||||
error: null,
|
||||
branch: [],
|
||||
fetched: [],
|
||||
resolve_opts
|
||||
});
|
||||
}
|
||||
|
||||
if (route) {
|
||||
const method = /** @type {import('types').HttpMethod} */ (event.request.method);
|
||||
|
||||
/** @type {Response} */
|
||||
let response;
|
||||
|
||||
if (is_data_request) {
|
||||
response = await render_data(
|
||||
event,
|
||||
route,
|
||||
options,
|
||||
manifest,
|
||||
state,
|
||||
invalidated_data_nodes,
|
||||
trailing_slash ?? 'never'
|
||||
);
|
||||
} else if (route.endpoint && (!route.page || is_endpoint_request(event))) {
|
||||
response = await render_endpoint(event, await route.endpoint(), state);
|
||||
} else if (route.page) {
|
||||
if (page_methods.has(method)) {
|
||||
response = await render_page(event, route.page, options, manifest, state, resolve_opts);
|
||||
} else {
|
||||
const allowed_methods = new Set(allowed_page_methods);
|
||||
const node = await manifest._.nodes[route.page.leaf]();
|
||||
if (node?.server?.actions) {
|
||||
allowed_methods.add('POST');
|
||||
}
|
||||
|
||||
if (method === 'OPTIONS') {
|
||||
// This will deny CORS preflight requests implicitly because we don't
|
||||
// add the required CORS headers to the response.
|
||||
response = new Response(null, {
|
||||
status: 204,
|
||||
headers: {
|
||||
allow: Array.from(allowed_methods.values()).join(', ')
|
||||
}
|
||||
});
|
||||
} else {
|
||||
const mod = [...allowed_methods].reduce((acc, curr) => {
|
||||
acc[curr] = true;
|
||||
return acc;
|
||||
}, /** @type {Record<string, any>} */ ({}));
|
||||
response = method_not_allowed(mod, method);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// a route will always have a page or an endpoint, but TypeScript
|
||||
// doesn't know that
|
||||
throw new Error('This should never happen');
|
||||
}
|
||||
|
||||
// If the route contains a page and an endpoint, we need to add a
|
||||
// `Vary: Accept` header to the response because of browser caching
|
||||
if (request.method === 'GET' && route.page && route.endpoint) {
|
||||
const vary = response.headers
|
||||
.get('vary')
|
||||
?.split(',')
|
||||
?.map((v) => v.trim().toLowerCase());
|
||||
if (!(vary?.includes('accept') || vary?.includes('*'))) {
|
||||
// the returned response might have immutable headers,
|
||||
// so we have to clone them before trying to mutate them
|
||||
response = new Response(response.body, {
|
||||
status: response.status,
|
||||
statusText: response.statusText,
|
||||
headers: new Headers(response.headers)
|
||||
});
|
||||
response.headers.append('Vary', 'Accept');
|
||||
}
|
||||
}
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
if (state.error && event.isSubRequest) {
|
||||
// avoid overwriting the headers. This could be a same origin fetch request
|
||||
// to an external service from the root layout while rendering an error page
|
||||
const headers = new Headers(request.headers);
|
||||
headers.set('x-sveltekit-error', 'true');
|
||||
return await fetch(request, { headers });
|
||||
}
|
||||
|
||||
if (state.error) {
|
||||
return text('Internal Server Error', {
|
||||
status: 500
|
||||
});
|
||||
}
|
||||
|
||||
// if this request came direct from the user, rather than
|
||||
// via our own `fetch`, render a 404 page
|
||||
if (state.depth === 0) {
|
||||
return await respond_with_error({
|
||||
event,
|
||||
options,
|
||||
manifest,
|
||||
state,
|
||||
status: 404,
|
||||
error: new SvelteKitError(404, 'Not Found', `Not found: ${event.url.pathname}`),
|
||||
resolve_opts
|
||||
});
|
||||
}
|
||||
|
||||
if (state.prerendering) {
|
||||
return text('not found', { status: 404 });
|
||||
}
|
||||
|
||||
// we can't load the endpoint from our own manifest,
|
||||
// so we need to make an actual HTTP request
|
||||
return await fetch(request);
|
||||
} catch (e) {
|
||||
// TODO if `e` is instead named `error`, some fucked up Vite transformation happens
|
||||
// and I don't even know how to describe it. need to investigate at some point
|
||||
|
||||
// HttpError from endpoint can end up here - TODO should it be handled there instead?
|
||||
return await handle_fatal_error(event, options, e);
|
||||
} finally {
|
||||
event.cookies.set = () => {
|
||||
throw new Error('Cannot use `cookies.set(...)` after the response has been generated');
|
||||
};
|
||||
|
||||
event.setHeaders = () => {
|
||||
throw new Error('Cannot use `setHeaders(...)` after the response has been generated');
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user