feat: docker compose maybe
This commit is contained in:
1
node_modules/@sveltejs/kit/src/runtime/app/env.js
generated
vendored
Normal file
1
node_modules/@sveltejs/kit/src/runtime/app/env.js
generated
vendored
Normal file
@ -0,0 +1 @@
|
||||
throw new Error('$app/env has been renamed to $app/environment');
|
12
node_modules/@sveltejs/kit/src/runtime/app/environment.js
generated
vendored
Normal file
12
node_modules/@sveltejs/kit/src/runtime/app/environment.js
generated
vendored
Normal file
@ -0,0 +1,12 @@
|
||||
import { BROWSER, DEV } from 'esm-env';
|
||||
export { building, version } from '__sveltekit/environment';
|
||||
|
||||
/**
|
||||
* `true` if the app is running in the browser.
|
||||
*/
|
||||
export const browser = BROWSER;
|
||||
|
||||
/**
|
||||
* Whether the dev server is running. This is not guaranteed to correspond to `NODE_ENV` or `MODE`.
|
||||
*/
|
||||
export const dev = DEV;
|
254
node_modules/@sveltejs/kit/src/runtime/app/forms.js
generated
vendored
Normal file
254
node_modules/@sveltejs/kit/src/runtime/app/forms.js
generated
vendored
Normal file
@ -0,0 +1,254 @@
|
||||
import * as devalue from 'devalue';
|
||||
import { BROWSER, DEV } from 'esm-env';
|
||||
import { client } from '../client/singletons.js';
|
||||
import { invalidateAll } from './navigation.js';
|
||||
|
||||
/**
|
||||
* This action updates the `form` property of the current page with the given data and updates `$page.status`.
|
||||
* In case of an error, it redirects to the nearest error page.
|
||||
* @template {Record<string, unknown> | undefined} Success
|
||||
* @template {Record<string, unknown> | undefined} Failure
|
||||
* @param {import('@sveltejs/kit').ActionResult<Success, Failure>} result
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
export function applyAction(result) {
|
||||
if (BROWSER) {
|
||||
return client.apply_action(result);
|
||||
} else {
|
||||
throw new Error('Cannot call applyAction(...) on the server');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Use this function to deserialize the response from a form submission.
|
||||
* Usage:
|
||||
*
|
||||
* ```js
|
||||
* import { deserialize } from '$app/forms';
|
||||
*
|
||||
* async function handleSubmit(event) {
|
||||
* const response = await fetch('/form?/action', {
|
||||
* method: 'POST',
|
||||
* body: new FormData(event.target)
|
||||
* });
|
||||
*
|
||||
* const result = deserialize(await response.text());
|
||||
* // ...
|
||||
* }
|
||||
* ```
|
||||
* @template {Record<string, unknown> | undefined} Success
|
||||
* @template {Record<string, unknown> | undefined} Failure
|
||||
* @param {string} result
|
||||
* @returns {import('@sveltejs/kit').ActionResult<Success, Failure>}
|
||||
*/
|
||||
export function deserialize(result) {
|
||||
const parsed = JSON.parse(result);
|
||||
if (parsed.data) {
|
||||
parsed.data = devalue.parse(parsed.data);
|
||||
}
|
||||
return parsed;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} old_name
|
||||
* @param {string} new_name
|
||||
* @param {string} call_location
|
||||
* @returns void
|
||||
*/
|
||||
function warn_on_access(old_name, new_name, call_location) {
|
||||
if (!DEV) return;
|
||||
// TODO 2.0: Remove this code
|
||||
console.warn(
|
||||
`\`${old_name}\` has been deprecated in favor of \`${new_name}\`. \`${old_name}\` will be removed in a future version. (Called from ${call_location})`
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Shallow clone an element, so that we can access e.g. `form.action` without worrying
|
||||
* that someone has added an `<input name="action">` (https://github.com/sveltejs/kit/issues/7593)
|
||||
* @template {HTMLElement} T
|
||||
* @param {T} element
|
||||
* @returns {T}
|
||||
*/
|
||||
function clone(element) {
|
||||
return /** @type {T} */ (HTMLElement.prototype.cloneNode.call(element));
|
||||
}
|
||||
|
||||
/**
|
||||
* This action enhances a `<form>` element that otherwise would work without JavaScript.
|
||||
*
|
||||
* The `submit` function is called upon submission with the given FormData and the `action` that should be triggered.
|
||||
* If `cancel` is called, the form will not be submitted.
|
||||
* You can use the abort `controller` to cancel the submission in case another one starts.
|
||||
* If a function is returned, that function is called with the response from the server.
|
||||
* If nothing is returned, the fallback will be used.
|
||||
*
|
||||
* If this function or its return value isn't set, it
|
||||
* - falls back to updating the `form` prop with the returned data if the action is one same page as the form
|
||||
* - updates `$page.status`
|
||||
* - resets the `<form>` element and invalidates all data in case of successful submission with no redirect response
|
||||
* - redirects in case of a redirect response
|
||||
* - redirects to the nearest error page in case of an unexpected error
|
||||
*
|
||||
* If you provide a custom function with a callback and want to use the default behavior, invoke `update` in your callback.
|
||||
* @template {Record<string, unknown> | undefined} Success
|
||||
* @template {Record<string, unknown> | undefined} Failure
|
||||
* @param {HTMLFormElement} form_element The form element
|
||||
* @param {import('@sveltejs/kit').SubmitFunction<Success, Failure>} submit Submit callback
|
||||
*/
|
||||
export function enhance(form_element, submit = () => {}) {
|
||||
if (DEV && clone(form_element).method !== 'post') {
|
||||
throw new Error('use:enhance can only be used on <form> fields with method="POST"');
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {{
|
||||
* action: URL;
|
||||
* invalidateAll?: boolean;
|
||||
* result: import('@sveltejs/kit').ActionResult;
|
||||
* reset?: boolean
|
||||
* }} opts
|
||||
*/
|
||||
const fallback_callback = async ({
|
||||
action,
|
||||
result,
|
||||
reset = true,
|
||||
invalidateAll: shouldInvalidateAll = true
|
||||
}) => {
|
||||
if (result.type === 'success') {
|
||||
if (reset) {
|
||||
// We call reset from the prototype to avoid DOM clobbering
|
||||
HTMLFormElement.prototype.reset.call(form_element);
|
||||
}
|
||||
if (shouldInvalidateAll) {
|
||||
await invalidateAll();
|
||||
}
|
||||
}
|
||||
|
||||
// For success/failure results, only apply action if it belongs to the
|
||||
// current page, otherwise `form` will be updated erroneously
|
||||
if (
|
||||
location.origin + location.pathname === action.origin + action.pathname ||
|
||||
result.type === 'redirect' ||
|
||||
result.type === 'error'
|
||||
) {
|
||||
applyAction(result);
|
||||
}
|
||||
};
|
||||
|
||||
/** @param {SubmitEvent} event */
|
||||
async function handle_submit(event) {
|
||||
const method = event.submitter?.hasAttribute('formmethod')
|
||||
? /** @type {HTMLButtonElement | HTMLInputElement} */ (event.submitter).formMethod
|
||||
: clone(form_element).method;
|
||||
if (method !== 'post') return;
|
||||
|
||||
event.preventDefault();
|
||||
|
||||
const action = new URL(
|
||||
// We can't do submitter.formAction directly because that property is always set
|
||||
event.submitter?.hasAttribute('formaction')
|
||||
? /** @type {HTMLButtonElement | HTMLInputElement} */ (event.submitter).formAction
|
||||
: clone(form_element).action
|
||||
);
|
||||
|
||||
const form_data = new FormData(form_element);
|
||||
|
||||
if (DEV && clone(form_element).enctype !== 'multipart/form-data') {
|
||||
for (const value of form_data.values()) {
|
||||
if (value instanceof File) {
|
||||
// TODO 2.0: Upgrade to `throw Error`
|
||||
console.warn(
|
||||
'Your form contains <input type="file"> fields, but is missing the `enctype="multipart/form-data"` attribute. This will lead to inconsistent behavior between enhanced and native forms. For more details, see https://github.com/sveltejs/kit/issues/9819. This will be upgraded to an error in v2.0.'
|
||||
);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const submitter_name = event.submitter?.getAttribute('name');
|
||||
if (submitter_name) {
|
||||
form_data.append(submitter_name, event.submitter?.getAttribute('value') ?? '');
|
||||
}
|
||||
|
||||
const controller = new AbortController();
|
||||
|
||||
let cancelled = false;
|
||||
const cancel = () => (cancelled = true);
|
||||
|
||||
// TODO 2.0: Remove `data` and `form`
|
||||
const callback =
|
||||
(await submit({
|
||||
action,
|
||||
cancel,
|
||||
controller,
|
||||
get data() {
|
||||
warn_on_access('data', 'formData', 'use:enhance submit function');
|
||||
return form_data;
|
||||
},
|
||||
formData: form_data,
|
||||
get form() {
|
||||
warn_on_access('form', 'formElement', 'use:enhance submit function');
|
||||
return form_element;
|
||||
},
|
||||
formElement: form_element,
|
||||
submitter: event.submitter
|
||||
})) ?? fallback_callback;
|
||||
if (cancelled) return;
|
||||
|
||||
/** @type {import('@sveltejs/kit').ActionResult} */
|
||||
let result;
|
||||
|
||||
try {
|
||||
const response = await fetch(action, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
accept: 'application/json',
|
||||
'x-sveltekit-action': 'true'
|
||||
},
|
||||
cache: 'no-store',
|
||||
body: form_data,
|
||||
signal: controller.signal
|
||||
});
|
||||
|
||||
result = deserialize(await response.text());
|
||||
if (result.type === 'error') result.status = response.status;
|
||||
} catch (error) {
|
||||
if (/** @type {any} */ (error)?.name === 'AbortError') return;
|
||||
result = { type: 'error', error };
|
||||
}
|
||||
|
||||
callback({
|
||||
action,
|
||||
get data() {
|
||||
warn_on_access('data', 'formData', 'callback returned from use:enhance submit function');
|
||||
return form_data;
|
||||
},
|
||||
formData: form_data,
|
||||
get form() {
|
||||
warn_on_access('form', 'formElement', 'callback returned from use:enhance submit function');
|
||||
return form_element;
|
||||
},
|
||||
formElement: form_element,
|
||||
update: (opts) =>
|
||||
fallback_callback({
|
||||
action,
|
||||
result,
|
||||
reset: opts?.reset,
|
||||
invalidateAll: opts?.invalidateAll
|
||||
}),
|
||||
// @ts-expect-error generic constraints stuff we don't care about
|
||||
result
|
||||
});
|
||||
}
|
||||
|
||||
// @ts-expect-error
|
||||
HTMLFormElement.prototype.addEventListener.call(form_element, 'submit', handle_submit);
|
||||
|
||||
return {
|
||||
destroy() {
|
||||
// @ts-expect-error
|
||||
HTMLFormElement.prototype.removeEventListener.call(form_element, 'submit', handle_submit);
|
||||
}
|
||||
};
|
||||
}
|
126
node_modules/@sveltejs/kit/src/runtime/app/navigation.js
generated
vendored
Normal file
126
node_modules/@sveltejs/kit/src/runtime/app/navigation.js
generated
vendored
Normal file
@ -0,0 +1,126 @@
|
||||
import { client_method } from '../client/singletons.js';
|
||||
|
||||
/**
|
||||
* If called when the page is being updated following a navigation (in `onMount` or `afterNavigate` or an action, for example), this disables SvelteKit's built-in scroll handling.
|
||||
* This is generally discouraged, since it breaks user expectations.
|
||||
* @returns {void}
|
||||
*/
|
||||
export const disableScrollHandling = /* @__PURE__ */ client_method('disable_scroll_handling');
|
||||
|
||||
/**
|
||||
* Returns a Promise that resolves when SvelteKit navigates (or fails to navigate, in which case the promise rejects) to the specified `url`.
|
||||
* For external URLs, use `window.location = url` instead of calling `goto(url)`.
|
||||
*
|
||||
* @type {(url: string | URL, opts?: {
|
||||
* replaceState?: boolean;
|
||||
* noScroll?: boolean;
|
||||
* keepFocus?: boolean;
|
||||
* invalidateAll?: boolean;
|
||||
* state?: any
|
||||
* }) => Promise<void>}
|
||||
* @param {string | URL} url Where to navigate to. Note that if you've set [`config.kit.paths.base`](https://kit.svelte.dev/docs/configuration#paths) and the URL is root-relative, you need to prepend the base path if you want to navigate within the app.
|
||||
* @param {Object} [opts] Options related to the navigation
|
||||
* @param {boolean} [opts.replaceState] If `true`, will replace the current `history` entry rather than creating a new one with `pushState`
|
||||
* @param {boolean} [opts.noScroll] If `true`, the browser will maintain its scroll position rather than scrolling to the top of the page after navigation
|
||||
* @param {boolean} [opts.keepFocus] If `true`, the currently focused element will retain focus after navigation. Otherwise, focus will be reset to the body
|
||||
* @param {boolean} [invalidateAll] If `true`, all `load` functions of the page will be rerun. See https://kit.svelte.dev/docs/load#rerunning-load-functions for more info on invalidation.
|
||||
* @param {any} [opts.state] The state of the new/updated history entry
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
export const goto = /* @__PURE__ */ client_method('goto');
|
||||
|
||||
/**
|
||||
* Causes any `load` functions belonging to the currently active page to re-run if they depend on the `url` in question, via `fetch` or `depends`. Returns a `Promise` that resolves when the page is subsequently updated.
|
||||
*
|
||||
* If the argument is given as a `string` or `URL`, it must resolve to the same URL that was passed to `fetch` or `depends` (including query parameters).
|
||||
* To create a custom identifier, use a string beginning with `[a-z]+:` (e.g. `custom:state`) — this is a valid URL.
|
||||
*
|
||||
* The `function` argument can be used define a custom predicate. It receives the full `URL` and causes `load` to rerun if `true` is returned.
|
||||
* This can be useful if you want to invalidate based on a pattern instead of a exact match.
|
||||
*
|
||||
* ```ts
|
||||
* // Example: Match '/path' regardless of the query parameters
|
||||
* import { invalidate } from '$app/navigation';
|
||||
*
|
||||
* invalidate((url) => url.pathname === '/path');
|
||||
* ```
|
||||
* @type {(url: string | URL | ((url: URL) => boolean)) => Promise<void>}
|
||||
* @param {string | URL | ((url: URL) => boolean)} url The invalidated URL
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
export const invalidate = /* @__PURE__ */ client_method('invalidate');
|
||||
|
||||
/**
|
||||
* Causes all `load` functions belonging to the currently active page to re-run. Returns a `Promise` that resolves when the page is subsequently updated.
|
||||
* @type {() => Promise<void>}
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
export const invalidateAll = /* @__PURE__ */ client_method('invalidate_all');
|
||||
|
||||
/**
|
||||
* Programmatically preloads the given page, which means
|
||||
* 1. ensuring that the code for the page is loaded, and
|
||||
* 2. calling the page's load function with the appropriate options.
|
||||
*
|
||||
* This is the same behaviour that SvelteKit triggers when the user taps or mouses over an `<a>` element with `data-sveltekit-preload-data`.
|
||||
* If the next navigation is to `href`, the values returned from load will be used, making navigation instantaneous.
|
||||
* Returns a Promise that resolves when the preload is complete.
|
||||
*
|
||||
* @type {(href: string) => Promise<void>}
|
||||
* @param {string} href Page to preload
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
export const preloadData = /* @__PURE__ */ client_method('preload_data');
|
||||
|
||||
/**
|
||||
* Programmatically imports the code for routes that haven't yet been fetched.
|
||||
* Typically, you might call this to speed up subsequent navigation.
|
||||
*
|
||||
* You can specify routes by any matching pathname such as `/about` (to match `src/routes/about/+page.svelte`) or `/blog/*` (to match `src/routes/blog/[slug]/+page.svelte`).
|
||||
*
|
||||
* Unlike `preloadData`, this won't call `load` functions.
|
||||
* Returns a Promise that resolves when the modules have been imported.
|
||||
*
|
||||
* @type {(...urls: string[]) => Promise<void>}
|
||||
* @param {...string[]} urls
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
export const preloadCode = /* @__PURE__ */ client_method('preload_code');
|
||||
|
||||
/**
|
||||
* A navigation interceptor that triggers before we navigate to a new URL, whether by clicking a link, calling `goto(...)`, or using the browser back/forward controls.
|
||||
* Calling `cancel()` will prevent the navigation from completing. If the navigation would have directly unloaded the current page, calling `cancel` will trigger the native
|
||||
* browser unload confirmation dialog. In these cases, `navigation.willUnload` is `true`.
|
||||
*
|
||||
* When a navigation isn't client side, `navigation.to.route.id` will be `null`.
|
||||
*
|
||||
* `beforeNavigate` must be called during a component initialization. It remains active as long as the component is mounted.
|
||||
* @type {(callback: (navigation: import('@sveltejs/kit').BeforeNavigate) => void) => void}
|
||||
* @param {(navigation: import('@sveltejs/kit').BeforeNavigate) => void} callback
|
||||
* @returns {void}
|
||||
*/
|
||||
export const beforeNavigate = /* @__PURE__ */ client_method('before_navigate');
|
||||
|
||||
/**
|
||||
* A lifecycle function that runs the supplied `callback` immediately before we navigate to a new URL.
|
||||
*
|
||||
* If you return a `Promise`, SvelteKit will wait for it to resolve before completing the navigation. This allows you to — for example — use `document.startViewTransition`. Avoid promises that are slow to resolve, since navigation will appear stalled to the user.
|
||||
*
|
||||
* If a function (or a `Promise` that resolves to a function) is returned from the callback, it will be called once the DOM has updated.
|
||||
*
|
||||
* `onNavigate` must be called during a component initialization. It remains active as long as the component is mounted.
|
||||
* @type {(callback: (navigation: import('@sveltejs/kit').OnNavigate) => import('../../types/internal.js').MaybePromise<(() => void) | void>) => void}
|
||||
* @param {(navigation: import('@sveltejs/kit').OnNavigate) => void} callback
|
||||
* @returns {void}
|
||||
*/
|
||||
export const onNavigate = /* @__PURE__ */ client_method('on_navigate');
|
||||
|
||||
/**
|
||||
* A lifecycle function that runs the supplied `callback` when the current component mounts, and also whenever we navigate to a new URL.
|
||||
*
|
||||
* `afterNavigate` must be called during a component initialization. It remains active as long as the component is mounted.
|
||||
* @type {(callback: (navigation: import('@sveltejs/kit').AfterNavigate) => void) => void}
|
||||
* @param {(navigation: import('@sveltejs/kit').AfterNavigate) => void} callback
|
||||
* @returns {void}
|
||||
*/
|
||||
export const afterNavigate = /* @__PURE__ */ client_method('after_navigate');
|
1
node_modules/@sveltejs/kit/src/runtime/app/paths.js
generated
vendored
Normal file
1
node_modules/@sveltejs/kit/src/runtime/app/paths.js
generated
vendored
Normal file
@ -0,0 +1 @@
|
||||
export { base, assets } from '__sveltekit/paths';
|
94
node_modules/@sveltejs/kit/src/runtime/app/stores.js
generated
vendored
Normal file
94
node_modules/@sveltejs/kit/src/runtime/app/stores.js
generated
vendored
Normal file
@ -0,0 +1,94 @@
|
||||
import { getContext } from 'svelte';
|
||||
import { browser } from './environment.js';
|
||||
import { stores as browser_stores } from '../client/singletons.js';
|
||||
|
||||
/**
|
||||
* A function that returns all of the contextual stores. On the server, this must be called during component initialization.
|
||||
* Only use this if you need to defer store subscription until after the component has mounted, for some reason.
|
||||
*/
|
||||
export const getStores = () => {
|
||||
const stores = browser ? browser_stores : getContext('__svelte__');
|
||||
|
||||
return {
|
||||
/** @type {typeof page} */
|
||||
page: {
|
||||
subscribe: stores.page.subscribe
|
||||
},
|
||||
/** @type {typeof navigating} */
|
||||
navigating: {
|
||||
subscribe: stores.navigating.subscribe
|
||||
},
|
||||
/** @type {typeof updated} */
|
||||
updated: stores.updated
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* A readable store whose value contains page data.
|
||||
*
|
||||
* On the server, this store can only be subscribed to during component initialization. In the browser, it can be subscribed to at any time.
|
||||
*
|
||||
* @type {import('svelte/store').Readable<import('@sveltejs/kit').Page>}
|
||||
*/
|
||||
export const page = {
|
||||
subscribe(fn) {
|
||||
const store = __SVELTEKIT_DEV__ ? get_store('page') : getStores().page;
|
||||
return store.subscribe(fn);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* A readable store.
|
||||
* When navigating starts, its value is a `Navigation` object with `from`, `to`, `type` and (if `type === 'popstate'`) `delta` properties.
|
||||
* When navigating finishes, its value reverts to `null`.
|
||||
*
|
||||
* On the server, this store can only be subscribed to during component initialization. In the browser, it can be subscribed to at any time.
|
||||
* @type {import('svelte/store').Readable<import('@sveltejs/kit').Navigation | null>}
|
||||
*/
|
||||
export const navigating = {
|
||||
subscribe(fn) {
|
||||
const store = __SVELTEKIT_DEV__ ? get_store('navigating') : getStores().navigating;
|
||||
return store.subscribe(fn);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* A readable store whose initial value is `false`. If [`version.pollInterval`](https://kit.svelte.dev/docs/configuration#version) is a non-zero value, SvelteKit will poll for new versions of the app and update the store value to `true` when it detects one. `updated.check()` will force an immediate check, regardless of polling.
|
||||
*
|
||||
* On the server, this store can only be subscribed to during component initialization. In the browser, it can be subscribed to at any time.
|
||||
* @type {import('svelte/store').Readable<boolean> & { check(): Promise<boolean> }}
|
||||
*/
|
||||
export const updated = {
|
||||
subscribe(fn) {
|
||||
const store = __SVELTEKIT_DEV__ ? get_store('updated') : getStores().updated;
|
||||
|
||||
if (browser) {
|
||||
updated.check = store.check;
|
||||
}
|
||||
|
||||
return store.subscribe(fn);
|
||||
},
|
||||
check: () => {
|
||||
throw new Error(
|
||||
browser
|
||||
? 'Cannot check updated store before subscribing'
|
||||
: 'Can only check updated store in browser'
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @template {keyof ReturnType<typeof getStores>} Name
|
||||
* @param {Name} name
|
||||
* @returns {ReturnType<typeof getStores>[Name]}
|
||||
*/
|
||||
function get_store(name) {
|
||||
try {
|
||||
return getStores()[name];
|
||||
} catch (e) {
|
||||
throw new Error(
|
||||
`Cannot subscribe to '${name}' store on the server outside of a Svelte component, as it is bound to the current request via component context. This prevents state from leaking between users.` +
|
||||
'For more information, see https://kit.svelte.dev/docs/state-management#avoid-shared-state-on-the-server'
|
||||
);
|
||||
}
|
||||
}
|
2090
node_modules/@sveltejs/kit/src/runtime/client/client.js
generated
vendored
Normal file
2090
node_modules/@sveltejs/kit/src/runtime/client/client.js
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
11
node_modules/@sveltejs/kit/src/runtime/client/constants.js
generated
vendored
Normal file
11
node_modules/@sveltejs/kit/src/runtime/client/constants.js
generated
vendored
Normal file
@ -0,0 +1,11 @@
|
||||
export const SNAPSHOT_KEY = 'sveltekit:snapshot';
|
||||
export const SCROLL_KEY = 'sveltekit:scroll';
|
||||
export const INDEX_KEY = 'sveltekit:index';
|
||||
|
||||
export const PRELOAD_PRIORITIES = /** @type {const} */ ({
|
||||
tap: 1,
|
||||
hover: 2,
|
||||
viewport: 3,
|
||||
eager: 4,
|
||||
off: -1
|
||||
});
|
167
node_modules/@sveltejs/kit/src/runtime/client/fetcher.js
generated
vendored
Normal file
167
node_modules/@sveltejs/kit/src/runtime/client/fetcher.js
generated
vendored
Normal file
@ -0,0 +1,167 @@
|
||||
import { DEV } from 'esm-env';
|
||||
import { hash } from '../hash.js';
|
||||
|
||||
let loading = 0;
|
||||
|
||||
export const native_fetch = window.fetch;
|
||||
|
||||
export function lock_fetch() {
|
||||
loading += 1;
|
||||
}
|
||||
|
||||
export function unlock_fetch() {
|
||||
loading -= 1;
|
||||
}
|
||||
|
||||
if (DEV) {
|
||||
let can_inspect_stack_trace = false;
|
||||
|
||||
const check_stack_trace = async () => {
|
||||
const stack = /** @type {string} */ (new Error().stack);
|
||||
can_inspect_stack_trace = stack.includes('check_stack_trace');
|
||||
};
|
||||
|
||||
check_stack_trace();
|
||||
|
||||
/**
|
||||
* @param {RequestInfo | URL} input
|
||||
* @param {RequestInit & Record<string, any> | undefined} init
|
||||
*/
|
||||
window.fetch = (input, init) => {
|
||||
// Check if fetch was called via load_node. the lock method only checks if it was called at the
|
||||
// same time, but not necessarily if it was called from `load`.
|
||||
// We use just the filename as the method name sometimes does not appear on the CI.
|
||||
const url = input instanceof Request ? input.url : input.toString();
|
||||
const stack_array = /** @type {string} */ (new Error().stack).split('\n');
|
||||
// We need to do a cutoff because Safari and Firefox maintain the stack
|
||||
// across events and for example traces a `fetch` call triggered from a button
|
||||
// back to the creation of the event listener and the element creation itself,
|
||||
// where at some point client.js will show up, leading to false positives.
|
||||
const cutoff = stack_array.findIndex((a) => a.includes('load@') || a.includes('at load'));
|
||||
const stack = stack_array.slice(0, cutoff + 2).join('\n');
|
||||
|
||||
const in_load_heuristic = can_inspect_stack_trace
|
||||
? stack.includes('src/runtime/client/client.js')
|
||||
: loading;
|
||||
|
||||
// This flag is set in initial_fetch and subsequent_fetch
|
||||
const used_kit_fetch = init?.__sveltekit_fetch__;
|
||||
|
||||
if (in_load_heuristic && !used_kit_fetch) {
|
||||
console.warn(
|
||||
`Loading ${url} using \`window.fetch\`. For best results, use the \`fetch\` that is passed to your \`load\` function: https://kit.svelte.dev/docs/load#making-fetch-requests`
|
||||
);
|
||||
}
|
||||
|
||||
const method = input instanceof Request ? input.method : init?.method || 'GET';
|
||||
|
||||
if (method !== 'GET') {
|
||||
cache.delete(build_selector(input));
|
||||
}
|
||||
|
||||
return native_fetch(input, init);
|
||||
};
|
||||
} else {
|
||||
window.fetch = (input, init) => {
|
||||
const method = input instanceof Request ? input.method : init?.method || 'GET';
|
||||
|
||||
if (method !== 'GET') {
|
||||
cache.delete(build_selector(input));
|
||||
}
|
||||
|
||||
return native_fetch(input, init);
|
||||
};
|
||||
}
|
||||
|
||||
const cache = new Map();
|
||||
|
||||
/**
|
||||
* Should be called on the initial run of load functions that hydrate the page.
|
||||
* Saves any requests with cache-control max-age to the cache.
|
||||
* @param {URL | string} resource
|
||||
* @param {RequestInit} [opts]
|
||||
*/
|
||||
export function initial_fetch(resource, opts) {
|
||||
const selector = build_selector(resource, opts);
|
||||
|
||||
const script = document.querySelector(selector);
|
||||
if (script?.textContent) {
|
||||
const { body, ...init } = JSON.parse(script.textContent);
|
||||
|
||||
const ttl = script.getAttribute('data-ttl');
|
||||
if (ttl) cache.set(selector, { body, init, ttl: 1000 * Number(ttl) });
|
||||
|
||||
return Promise.resolve(new Response(body, init));
|
||||
}
|
||||
|
||||
return DEV ? dev_fetch(resource, opts) : window.fetch(resource, opts);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tries to get the response from the cache, if max-age allows it, else does a fetch.
|
||||
* @param {URL | string} resource
|
||||
* @param {string} resolved
|
||||
* @param {RequestInit} [opts]
|
||||
*/
|
||||
export function subsequent_fetch(resource, resolved, opts) {
|
||||
if (cache.size > 0) {
|
||||
const selector = build_selector(resource, opts);
|
||||
const cached = cache.get(selector);
|
||||
if (cached) {
|
||||
// https://developer.mozilla.org/en-US/docs/Web/API/Request/cache#value
|
||||
if (
|
||||
performance.now() < cached.ttl &&
|
||||
['default', 'force-cache', 'only-if-cached', undefined].includes(opts?.cache)
|
||||
) {
|
||||
return new Response(cached.body, cached.init);
|
||||
}
|
||||
|
||||
cache.delete(selector);
|
||||
}
|
||||
}
|
||||
|
||||
return DEV ? dev_fetch(resolved, opts) : window.fetch(resolved, opts);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {RequestInfo | URL} resource
|
||||
* @param {RequestInit & Record<string, any> | undefined} opts
|
||||
*/
|
||||
function dev_fetch(resource, opts) {
|
||||
const patched_opts = { ...opts };
|
||||
// This assigns the __sveltekit_fetch__ flag and makes it non-enumerable
|
||||
Object.defineProperty(patched_opts, '__sveltekit_fetch__', {
|
||||
value: true,
|
||||
writable: true,
|
||||
configurable: true
|
||||
});
|
||||
return window.fetch(resource, patched_opts);
|
||||
}
|
||||
|
||||
/**
|
||||
* Build the cache key for a given request
|
||||
* @param {URL | RequestInfo} resource
|
||||
* @param {RequestInit} [opts]
|
||||
*/
|
||||
function build_selector(resource, opts) {
|
||||
const url = JSON.stringify(resource instanceof Request ? resource.url : resource);
|
||||
|
||||
let selector = `script[data-sveltekit-fetched][data-url=${url}]`;
|
||||
|
||||
if (opts?.headers || opts?.body) {
|
||||
/** @type {import('types').StrictBody[]} */
|
||||
const values = [];
|
||||
|
||||
if (opts.headers) {
|
||||
values.push([...new Headers(opts.headers)].join(','));
|
||||
}
|
||||
|
||||
if (opts.body && (typeof opts.body === 'string' || ArrayBuffer.isView(opts.body))) {
|
||||
values.push(opts.body);
|
||||
}
|
||||
|
||||
selector += `[data-hash="${hash(...values)}"]`;
|
||||
}
|
||||
|
||||
return selector;
|
||||
}
|
57
node_modules/@sveltejs/kit/src/runtime/client/parse.js
generated
vendored
Normal file
57
node_modules/@sveltejs/kit/src/runtime/client/parse.js
generated
vendored
Normal file
@ -0,0 +1,57 @@
|
||||
import { exec, parse_route_id } from '../../utils/routing.js';
|
||||
|
||||
/**
|
||||
* @param {import('./types.js').SvelteKitApp} app
|
||||
* @returns {import('types').CSRRoute[]}
|
||||
*/
|
||||
export function parse({ nodes, server_loads, dictionary, matchers }) {
|
||||
const layouts_with_server_load = new Set(server_loads);
|
||||
|
||||
return Object.entries(dictionary).map(([id, [leaf, layouts, errors]]) => {
|
||||
const { pattern, params } = parse_route_id(id);
|
||||
|
||||
const route = {
|
||||
id,
|
||||
/** @param {string} path */
|
||||
exec: (path) => {
|
||||
const match = pattern.exec(path);
|
||||
if (match) return exec(match, params, matchers);
|
||||
},
|
||||
errors: [1, ...(errors || [])].map((n) => nodes[n]),
|
||||
layouts: [0, ...(layouts || [])].map(create_layout_loader),
|
||||
leaf: create_leaf_loader(leaf)
|
||||
};
|
||||
|
||||
// bit of a hack, but ensures that layout/error node lists are the same
|
||||
// length, without which the wrong data will be applied if the route
|
||||
// manifest looks like `[[a, b], [c,], d]`
|
||||
route.errors.length = route.layouts.length = Math.max(
|
||||
route.errors.length,
|
||||
route.layouts.length
|
||||
);
|
||||
|
||||
return route;
|
||||
});
|
||||
|
||||
/**
|
||||
* @param {number} id
|
||||
* @returns {[boolean, import('types').CSRPageNodeLoader]}
|
||||
*/
|
||||
function create_leaf_loader(id) {
|
||||
// whether or not the route uses the server data is
|
||||
// encoded using the ones' complement, to save space
|
||||
const uses_server_data = id < 0;
|
||||
if (uses_server_data) id = ~id;
|
||||
return [uses_server_data, nodes[id]];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {number | undefined} id
|
||||
* @returns {[boolean, import('types').CSRPageNodeLoader] | undefined}
|
||||
*/
|
||||
function create_layout_loader(id) {
|
||||
// whether or not the layout uses the server data is
|
||||
// encoded in the layouts array, to save space
|
||||
return id === undefined ? id : [layouts_with_server_load.has(id), nodes[id]];
|
||||
}
|
||||
}
|
25
node_modules/@sveltejs/kit/src/runtime/client/session-storage.js
generated
vendored
Normal file
25
node_modules/@sveltejs/kit/src/runtime/client/session-storage.js
generated
vendored
Normal file
@ -0,0 +1,25 @@
|
||||
/**
|
||||
* Read a value from `sessionStorage`
|
||||
* @param {string} key
|
||||
*/
|
||||
export function get(key) {
|
||||
try {
|
||||
return JSON.parse(sessionStorage[key]);
|
||||
} catch {
|
||||
// do nothing
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Write a value to `sessionStorage`
|
||||
* @param {string} key
|
||||
* @param {any} value
|
||||
*/
|
||||
export function set(key, value) {
|
||||
const json = JSON.stringify(value);
|
||||
try {
|
||||
sessionStorage[key] = json;
|
||||
} catch {
|
||||
// do nothing
|
||||
}
|
||||
}
|
53
node_modules/@sveltejs/kit/src/runtime/client/singletons.js
generated
vendored
Normal file
53
node_modules/@sveltejs/kit/src/runtime/client/singletons.js
generated
vendored
Normal file
@ -0,0 +1,53 @@
|
||||
import { writable } from 'svelte/store';
|
||||
import { create_updated_store, notifiable_store } from './utils.js';
|
||||
import { BROWSER } from 'esm-env';
|
||||
|
||||
/** @type {import('./types.js').Client} */
|
||||
export let client;
|
||||
|
||||
/**
|
||||
* @param {{
|
||||
* client: import('./types.js').Client;
|
||||
* }} opts
|
||||
*/
|
||||
export function init(opts) {
|
||||
client = opts.client;
|
||||
}
|
||||
|
||||
/**
|
||||
* @template {keyof typeof client} T
|
||||
* @param {T} key
|
||||
* @returns {typeof client[T]}
|
||||
*/
|
||||
export function client_method(key) {
|
||||
if (!BROWSER) {
|
||||
if (key === 'before_navigate' || key === 'after_navigate' || key === 'on_navigate') {
|
||||
// @ts-expect-error doesn't recognize that both keys here return void so expects a async function
|
||||
return () => {};
|
||||
} else {
|
||||
/** @type {Record<string, string>} */
|
||||
const name_lookup = {
|
||||
disable_scroll_handling: 'disableScrollHandling',
|
||||
preload_data: 'preloadData',
|
||||
preload_code: 'preloadCode',
|
||||
invalidate_all: 'invalidateAll'
|
||||
};
|
||||
|
||||
return () => {
|
||||
throw new Error(`Cannot call ${name_lookup[key] ?? key}(...) on the server`);
|
||||
};
|
||||
}
|
||||
} else {
|
||||
// @ts-expect-error
|
||||
return (...args) => client[key](...args);
|
||||
}
|
||||
}
|
||||
|
||||
export const stores = {
|
||||
url: /* @__PURE__ */ notifiable_store({}),
|
||||
page: /* @__PURE__ */ notifiable_store({}),
|
||||
navigating: /* @__PURE__ */ writable(
|
||||
/** @type {import('@sveltejs/kit').Navigation | null} */ (null)
|
||||
),
|
||||
updated: /* @__PURE__ */ create_updated_store()
|
||||
};
|
28
node_modules/@sveltejs/kit/src/runtime/client/start.js
generated
vendored
Normal file
28
node_modules/@sveltejs/kit/src/runtime/client/start.js
generated
vendored
Normal file
@ -0,0 +1,28 @@
|
||||
import { DEV } from 'esm-env';
|
||||
import { create_client } from './client.js';
|
||||
import { init } from './singletons.js';
|
||||
|
||||
/**
|
||||
* @param {import('./types.js').SvelteKitApp} app
|
||||
* @param {HTMLElement} target
|
||||
* @param {Parameters<import('./types.js').Client['_hydrate']>[0]} [hydrate]
|
||||
*/
|
||||
export async function start(app, target, hydrate) {
|
||||
if (DEV && target === document.body) {
|
||||
console.warn(
|
||||
'Placing %sveltekit.body% directly inside <body> is not recommended, as your app may break for users who have certain browser extensions installed.\n\nConsider wrapping it in an element:\n\n<div style="display: contents">\n %sveltekit.body%\n</div>'
|
||||
);
|
||||
}
|
||||
|
||||
const client = create_client(app, target);
|
||||
|
||||
init({ client });
|
||||
|
||||
if (hydrate) {
|
||||
await client._hydrate(hydrate);
|
||||
} else {
|
||||
client.goto(location.href, { replaceState: true });
|
||||
}
|
||||
|
||||
client._start_router();
|
||||
}
|
123
node_modules/@sveltejs/kit/src/runtime/client/types.d.ts
generated
vendored
Normal file
123
node_modules/@sveltejs/kit/src/runtime/client/types.d.ts
generated
vendored
Normal file
@ -0,0 +1,123 @@
|
||||
import { applyAction } from '../app/forms.js';
|
||||
import {
|
||||
afterNavigate,
|
||||
beforeNavigate,
|
||||
onNavigate,
|
||||
goto,
|
||||
invalidate,
|
||||
invalidateAll,
|
||||
preloadCode,
|
||||
preloadData
|
||||
} from '../app/navigation.js';
|
||||
import { SvelteComponent } from 'svelte';
|
||||
import { ClientHooks, CSRPageNode, CSRPageNodeLoader, CSRRoute, TrailingSlash, Uses } from 'types';
|
||||
import { Page, ParamMatcher } from '@sveltejs/kit';
|
||||
|
||||
export interface SvelteKitApp {
|
||||
/**
|
||||
* A list of all the error/layout/page nodes used in the app
|
||||
*/
|
||||
nodes: CSRPageNodeLoader[];
|
||||
|
||||
/**
|
||||
* A list of all layout node ids that have a server load function.
|
||||
* Pages are not present because it's shorter to encode it on the leaf itself.
|
||||
*/
|
||||
server_loads: number[];
|
||||
|
||||
/**
|
||||
* A map of `[routeId: string]: [leaf, layouts, errors]` tuples, which
|
||||
* is parsed into an array of routes on startup. The numbers refer to the indices in `nodes`.
|
||||
* If the leaf number is negative, it means it does use a server load function and the complement is the node index.
|
||||
* The route layout and error nodes are not referenced, they are always number 0 and 1 and always apply.
|
||||
*/
|
||||
dictionary: Record<string, [leaf: number, layouts: number[], errors?: number[]]>;
|
||||
|
||||
matchers: Record<string, ParamMatcher>;
|
||||
|
||||
hooks: ClientHooks;
|
||||
|
||||
root: typeof SvelteComponent;
|
||||
}
|
||||
|
||||
export interface Client {
|
||||
// public API, exposed via $app/navigation
|
||||
after_navigate: typeof afterNavigate;
|
||||
before_navigate: typeof beforeNavigate;
|
||||
on_navigate: typeof onNavigate;
|
||||
disable_scroll_handling(): void;
|
||||
goto: typeof goto;
|
||||
invalidate: typeof invalidate;
|
||||
invalidate_all: typeof invalidateAll;
|
||||
preload_code: typeof preloadCode;
|
||||
preload_data: typeof preloadData;
|
||||
apply_action: typeof applyAction;
|
||||
|
||||
// private API
|
||||
_hydrate(opts: {
|
||||
status: number;
|
||||
error: App.Error | null;
|
||||
node_ids: number[];
|
||||
params: Record<string, string>;
|
||||
route: { id: string | null };
|
||||
data: Array<import('types').ServerDataNode | null>;
|
||||
form: Record<string, any> | null;
|
||||
}): Promise<void>;
|
||||
_start_router(): void;
|
||||
}
|
||||
|
||||
export type NavigationIntent = {
|
||||
/** `url.pathname + url.search` */
|
||||
id: string;
|
||||
/** Whether we are invalidating or navigating */
|
||||
invalidating: boolean;
|
||||
/** The route parameters */
|
||||
params: Record<string, string>;
|
||||
/** The route that matches `path` */
|
||||
route: CSRRoute;
|
||||
/** The destination URL */
|
||||
url: URL;
|
||||
};
|
||||
|
||||
export type NavigationResult = NavigationRedirect | NavigationFinished;
|
||||
|
||||
export type NavigationRedirect = {
|
||||
type: 'redirect';
|
||||
location: string;
|
||||
};
|
||||
|
||||
export type NavigationFinished = {
|
||||
type: 'loaded';
|
||||
state: NavigationState;
|
||||
props: {
|
||||
constructors: Array<typeof SvelteComponent>;
|
||||
components?: Array<SvelteComponent>;
|
||||
page?: Page;
|
||||
form?: Record<string, any> | null;
|
||||
[key: `data_${number}`]: Record<string, any>;
|
||||
};
|
||||
};
|
||||
|
||||
export type BranchNode = {
|
||||
node: CSRPageNode;
|
||||
loader: CSRPageNodeLoader;
|
||||
server: DataNode | null;
|
||||
universal: DataNode | null;
|
||||
data: Record<string, any> | null;
|
||||
slash?: TrailingSlash;
|
||||
};
|
||||
|
||||
export interface DataNode {
|
||||
type: 'data';
|
||||
data: Record<string, any> | null;
|
||||
uses: Uses;
|
||||
slash?: TrailingSlash;
|
||||
}
|
||||
|
||||
export interface NavigationState {
|
||||
branch: Array<BranchNode | undefined>;
|
||||
error: App.Error | null;
|
||||
params: Record<string, string>;
|
||||
route: CSRRoute | null;
|
||||
url: URL;
|
||||
}
|
296
node_modules/@sveltejs/kit/src/runtime/client/utils.js
generated
vendored
Normal file
296
node_modules/@sveltejs/kit/src/runtime/client/utils.js
generated
vendored
Normal file
@ -0,0 +1,296 @@
|
||||
import { BROWSER, DEV } from 'esm-env';
|
||||
import { writable } from 'svelte/store';
|
||||
import { assets } from '__sveltekit/paths';
|
||||
import { version } from '__sveltekit/environment';
|
||||
import { PRELOAD_PRIORITIES } from './constants.js';
|
||||
|
||||
/* global __SVELTEKIT_APP_VERSION_FILE__, __SVELTEKIT_APP_VERSION_POLL_INTERVAL__ */
|
||||
|
||||
export const origin = BROWSER ? location.origin : '';
|
||||
|
||||
/** @param {HTMLDocument} doc */
|
||||
export function get_base_uri(doc) {
|
||||
let baseURI = doc.baseURI;
|
||||
|
||||
if (!baseURI) {
|
||||
const baseTags = doc.getElementsByTagName('base');
|
||||
baseURI = baseTags.length ? baseTags[0].href : doc.URL;
|
||||
}
|
||||
|
||||
return baseURI;
|
||||
}
|
||||
|
||||
export function scroll_state() {
|
||||
return {
|
||||
x: pageXOffset,
|
||||
y: pageYOffset
|
||||
};
|
||||
}
|
||||
|
||||
const warned = new WeakSet();
|
||||
|
||||
/** @typedef {keyof typeof valid_link_options} LinkOptionName */
|
||||
|
||||
const valid_link_options = /** @type {const} */ ({
|
||||
'preload-code': ['', 'off', 'tap', 'hover', 'viewport', 'eager'],
|
||||
'preload-data': ['', 'off', 'tap', 'hover'],
|
||||
keepfocus: ['', 'true', 'off', 'false'],
|
||||
noscroll: ['', 'true', 'off', 'false'],
|
||||
reload: ['', 'true', 'off', 'false'],
|
||||
replacestate: ['', 'true', 'off', 'false']
|
||||
});
|
||||
|
||||
/**
|
||||
* @template {LinkOptionName} T
|
||||
* @typedef {typeof valid_link_options[T][number]} ValidLinkOptions
|
||||
*/
|
||||
|
||||
/**
|
||||
* @template {LinkOptionName} T
|
||||
* @param {Element} element
|
||||
* @param {T} name
|
||||
*/
|
||||
function link_option(element, name) {
|
||||
const value = /** @type {ValidLinkOptions<T> | null} */ (
|
||||
element.getAttribute(`data-sveltekit-${name}`)
|
||||
);
|
||||
|
||||
if (DEV) {
|
||||
validate_link_option(element, name, value);
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* @template {LinkOptionName} T
|
||||
* @template {ValidLinkOptions<T> | null} U
|
||||
* @param {Element} element
|
||||
* @param {T} name
|
||||
* @param {U} value
|
||||
*/
|
||||
function validate_link_option(element, name, value) {
|
||||
if (value === null) return;
|
||||
|
||||
// @ts-expect-error - includes is dumb
|
||||
if (!warned.has(element) && !valid_link_options[name].includes(value)) {
|
||||
console.error(
|
||||
`Unexpected value for ${name} — should be one of ${valid_link_options[name]
|
||||
.map((option) => JSON.stringify(option))
|
||||
.join(', ')}`,
|
||||
element
|
||||
);
|
||||
|
||||
warned.add(element);
|
||||
}
|
||||
}
|
||||
|
||||
const levels = {
|
||||
...PRELOAD_PRIORITIES,
|
||||
'': PRELOAD_PRIORITIES.hover
|
||||
};
|
||||
|
||||
/**
|
||||
* @param {Element} element
|
||||
* @returns {Element | null}
|
||||
*/
|
||||
function parent_element(element) {
|
||||
let parent = element.assignedSlot ?? element.parentNode;
|
||||
|
||||
// @ts-expect-error handle shadow roots
|
||||
if (parent?.nodeType === 11) parent = parent.host;
|
||||
|
||||
return /** @type {Element} */ (parent);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Element} element
|
||||
* @param {Element} target
|
||||
*/
|
||||
export function find_anchor(element, target) {
|
||||
while (element && element !== target) {
|
||||
if (element.nodeName.toUpperCase() === 'A' && element.hasAttribute('href')) {
|
||||
return /** @type {HTMLAnchorElement | SVGAElement} */ (element);
|
||||
}
|
||||
|
||||
element = /** @type {Element} */ (parent_element(element));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {HTMLAnchorElement | SVGAElement} a
|
||||
* @param {string} base
|
||||
*/
|
||||
export function get_link_info(a, base) {
|
||||
/** @type {URL | undefined} */
|
||||
let url;
|
||||
|
||||
try {
|
||||
url = new URL(a instanceof SVGAElement ? a.href.baseVal : a.href, document.baseURI);
|
||||
} catch {}
|
||||
|
||||
const target = a instanceof SVGAElement ? a.target.baseVal : a.target;
|
||||
|
||||
const external =
|
||||
!url ||
|
||||
!!target ||
|
||||
is_external_url(url, base) ||
|
||||
(a.getAttribute('rel') || '').split(/\s+/).includes('external');
|
||||
|
||||
const download = url?.origin === origin && a.hasAttribute('download');
|
||||
|
||||
return { url, external, target, download };
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {HTMLFormElement | HTMLAnchorElement | SVGAElement} element
|
||||
*/
|
||||
export function get_router_options(element) {
|
||||
/** @type {ValidLinkOptions<'keepfocus'> | null} */
|
||||
let keep_focus = null;
|
||||
|
||||
/** @type {ValidLinkOptions<'noscroll'> | null} */
|
||||
let noscroll = null;
|
||||
|
||||
/** @type {ValidLinkOptions<'preload-code'> | null} */
|
||||
let preload_code = null;
|
||||
|
||||
/** @type {ValidLinkOptions<'preload-data'> | null} */
|
||||
let preload_data = null;
|
||||
|
||||
/** @type {ValidLinkOptions<'reload'> | null} */
|
||||
let reload = null;
|
||||
|
||||
/** @type {ValidLinkOptions<'replacestate'> | null} */
|
||||
let replace_state = null;
|
||||
|
||||
/** @type {Element} */
|
||||
let el = element;
|
||||
|
||||
while (el && el !== document.documentElement) {
|
||||
if (preload_code === null) preload_code = link_option(el, 'preload-code');
|
||||
if (preload_data === null) preload_data = link_option(el, 'preload-data');
|
||||
if (keep_focus === null) keep_focus = link_option(el, 'keepfocus');
|
||||
if (noscroll === null) noscroll = link_option(el, 'noscroll');
|
||||
if (reload === null) reload = link_option(el, 'reload');
|
||||
if (replace_state === null) replace_state = link_option(el, 'replacestate');
|
||||
|
||||
el = /** @type {Element} */ (parent_element(el));
|
||||
}
|
||||
|
||||
/** @param {string | null} value */
|
||||
function get_option_state(value) {
|
||||
switch (value) {
|
||||
case '':
|
||||
case 'true':
|
||||
return true;
|
||||
case 'off':
|
||||
case 'false':
|
||||
return false;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
preload_code: levels[preload_code ?? 'off'],
|
||||
preload_data: levels[preload_data ?? 'off'],
|
||||
keep_focus: get_option_state(keep_focus),
|
||||
noscroll: get_option_state(noscroll),
|
||||
reload: get_option_state(reload),
|
||||
replace_state: get_option_state(replace_state)
|
||||
};
|
||||
}
|
||||
|
||||
/** @param {any} value */
|
||||
export function notifiable_store(value) {
|
||||
const store = writable(value);
|
||||
let ready = true;
|
||||
|
||||
function notify() {
|
||||
ready = true;
|
||||
store.update((val) => val);
|
||||
}
|
||||
|
||||
/** @param {any} new_value */
|
||||
function set(new_value) {
|
||||
ready = false;
|
||||
store.set(new_value);
|
||||
}
|
||||
|
||||
/** @param {(value: any) => void} run */
|
||||
function subscribe(run) {
|
||||
/** @type {any} */
|
||||
let old_value;
|
||||
return store.subscribe((new_value) => {
|
||||
if (old_value === undefined || (ready && new_value !== old_value)) {
|
||||
run((old_value = new_value));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return { notify, set, subscribe };
|
||||
}
|
||||
|
||||
export function create_updated_store() {
|
||||
const { set, subscribe } = writable(false);
|
||||
|
||||
if (DEV || !BROWSER) {
|
||||
return {
|
||||
subscribe,
|
||||
check: async () => false
|
||||
};
|
||||
}
|
||||
|
||||
const interval = __SVELTEKIT_APP_VERSION_POLL_INTERVAL__;
|
||||
|
||||
/** @type {NodeJS.Timeout} */
|
||||
let timeout;
|
||||
|
||||
/** @type {() => Promise<boolean>} */
|
||||
async function check() {
|
||||
clearTimeout(timeout);
|
||||
|
||||
if (interval) timeout = setTimeout(check, interval);
|
||||
|
||||
try {
|
||||
const res = await fetch(`${assets}/${__SVELTEKIT_APP_VERSION_FILE__}`, {
|
||||
headers: {
|
||||
pragma: 'no-cache',
|
||||
'cache-control': 'no-cache'
|
||||
}
|
||||
});
|
||||
|
||||
if (!res.ok) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const data = await res.json();
|
||||
const updated = data.version !== version;
|
||||
|
||||
if (updated) {
|
||||
set(true);
|
||||
clearTimeout(timeout);
|
||||
}
|
||||
|
||||
return updated;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (interval) timeout = setTimeout(check, interval);
|
||||
|
||||
return {
|
||||
subscribe,
|
||||
check
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {URL} url
|
||||
* @param {string} base
|
||||
*/
|
||||
export function is_external_url(url, base) {
|
||||
return url.origin !== origin || !url.pathname.startsWith(base);
|
||||
}
|
6
node_modules/@sveltejs/kit/src/runtime/components/error.svelte
generated
vendored
Normal file
6
node_modules/@sveltejs/kit/src/runtime/components/error.svelte
generated
vendored
Normal file
@ -0,0 +1,6 @@
|
||||
<script>
|
||||
import { page } from '$app/stores';
|
||||
</script>
|
||||
|
||||
<h1>{$page.status}</h1>
|
||||
<p>{$page.error?.message}</p>
|
1
node_modules/@sveltejs/kit/src/runtime/components/layout.svelte
generated
vendored
Normal file
1
node_modules/@sveltejs/kit/src/runtime/components/layout.svelte
generated
vendored
Normal file
@ -0,0 +1 @@
|
||||
<slot></slot>
|
66
node_modules/@sveltejs/kit/src/runtime/control.js
generated
vendored
Normal file
66
node_modules/@sveltejs/kit/src/runtime/control.js
generated
vendored
Normal file
@ -0,0 +1,66 @@
|
||||
export class HttpError {
|
||||
/**
|
||||
* @param {number} status
|
||||
* @param {{message: string} extends App.Error ? (App.Error | string | undefined) : App.Error} body
|
||||
*/
|
||||
constructor(status, body) {
|
||||
this.status = status;
|
||||
if (typeof body === 'string') {
|
||||
this.body = { message: body };
|
||||
} else if (body) {
|
||||
this.body = body;
|
||||
} else {
|
||||
this.body = { message: `Error: ${status}` };
|
||||
}
|
||||
}
|
||||
|
||||
toString() {
|
||||
return JSON.stringify(this.body);
|
||||
}
|
||||
}
|
||||
|
||||
export class Redirect {
|
||||
/**
|
||||
* @param {300 | 301 | 302 | 303 | 304 | 305 | 306 | 307 | 308} status
|
||||
* @param {string} location
|
||||
*/
|
||||
constructor(status, location) {
|
||||
this.status = status;
|
||||
this.location = location;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @template {Record<string, unknown> | undefined} [T=undefined]
|
||||
*/
|
||||
export class ActionFailure {
|
||||
/**
|
||||
* @param {number} status
|
||||
* @param {T} [data]
|
||||
*/
|
||||
constructor(status, data) {
|
||||
this.status = status;
|
||||
this.data = data;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This is a grotesque hack that, in dev, allows us to replace the implementations
|
||||
* of these classes that you'd get by importing them from `@sveltejs/kit` with the
|
||||
* ones that are imported via Vite and loaded internally, so that instanceof
|
||||
* checks work even though SvelteKit imports this module via Vite and consumers
|
||||
* import it via Node
|
||||
* @param {{
|
||||
* ActionFailure: typeof ActionFailure;
|
||||
* HttpError: typeof HttpError;
|
||||
* Redirect: typeof Redirect;
|
||||
* }} implementations
|
||||
*/
|
||||
export function replace_implementations(implementations) {
|
||||
// @ts-expect-error
|
||||
ActionFailure = implementations.ActionFailure; // eslint-disable-line no-class-assign
|
||||
// @ts-expect-error
|
||||
HttpError = implementations.HttpError; // eslint-disable-line no-class-assign
|
||||
// @ts-expect-error
|
||||
Redirect = implementations.Redirect; // eslint-disable-line no-class-assign
|
||||
}
|
1
node_modules/@sveltejs/kit/src/runtime/env/dynamic/private.js
generated
vendored
Normal file
1
node_modules/@sveltejs/kit/src/runtime/env/dynamic/private.js
generated
vendored
Normal file
@ -0,0 +1 @@
|
||||
export { private_env as env } from '../../shared-server.js';
|
1
node_modules/@sveltejs/kit/src/runtime/env/dynamic/public.js
generated
vendored
Normal file
1
node_modules/@sveltejs/kit/src/runtime/env/dynamic/public.js
generated
vendored
Normal file
@ -0,0 +1 @@
|
||||
export { public_env as env } from '../../shared-server.js';
|
22
node_modules/@sveltejs/kit/src/runtime/hash.js
generated
vendored
Normal file
22
node_modules/@sveltejs/kit/src/runtime/hash.js
generated
vendored
Normal file
@ -0,0 +1,22 @@
|
||||
/**
|
||||
* Hash using djb2
|
||||
* @param {import('types').StrictBody[]} values
|
||||
*/
|
||||
export function hash(...values) {
|
||||
let hash = 5381;
|
||||
|
||||
for (const value of values) {
|
||||
if (typeof value === 'string') {
|
||||
let i = value.length;
|
||||
while (i) hash = (hash * 33) ^ value.charCodeAt(--i);
|
||||
} else if (ArrayBuffer.isView(value)) {
|
||||
const buffer = new Uint8Array(value.buffer, value.byteOffset, value.byteLength);
|
||||
let i = buffer.length;
|
||||
while (i) hash = (hash * 33) ^ buffer[--i];
|
||||
} else {
|
||||
throw new TypeError('value must be a string or TypedArray');
|
||||
}
|
||||
}
|
||||
|
||||
return (hash >>> 0).toString(36);
|
||||
}
|
8
node_modules/@sveltejs/kit/src/runtime/server/ambient.d.ts
generated
vendored
Normal file
8
node_modules/@sveltejs/kit/src/runtime/server/ambient.d.ts
generated
vendored
Normal file
@ -0,0 +1,8 @@
|
||||
declare module '__SERVER__/internal.js' {
|
||||
export const options: import('types').SSROptions;
|
||||
export const get_hooks: () => Promise<{
|
||||
handle?: import('@sveltejs/kit').Handle;
|
||||
handleError?: import('@sveltejs/kit').HandleServerError;
|
||||
handleFetch?: import('@sveltejs/kit').HandleFetch;
|
||||
}>;
|
||||
}
|
250
node_modules/@sveltejs/kit/src/runtime/server/cookie.js
generated
vendored
Normal file
250
node_modules/@sveltejs/kit/src/runtime/server/cookie.js
generated
vendored
Normal file
@ -0,0 +1,250 @@
|
||||
import { parse, serialize } from 'cookie';
|
||||
import { normalize_path } from '../../utils/url.js';
|
||||
|
||||
/**
|
||||
* Tracks all cookies set during dev mode so we can emit warnings
|
||||
* when we detect that there's likely cookie misusage due to wrong paths
|
||||
*
|
||||
* @type {Record<string, Set<string>>} */
|
||||
const cookie_paths = {};
|
||||
|
||||
/**
|
||||
* Cookies that are larger than this size (including the name and other
|
||||
* attributes) are discarded by browsers
|
||||
*/
|
||||
const MAX_COOKIE_SIZE = 4129;
|
||||
|
||||
/**
|
||||
* @param {Request} request
|
||||
* @param {URL} url
|
||||
* @param {import('types').TrailingSlash} trailing_slash
|
||||
*/
|
||||
export function get_cookies(request, url, trailing_slash) {
|
||||
const header = request.headers.get('cookie') ?? '';
|
||||
const initial_cookies = parse(header, { decode: (value) => value });
|
||||
|
||||
const normalized_url = normalize_path(url.pathname, trailing_slash);
|
||||
// Emulate browser-behavior: if the cookie is set at '/foo/bar', its path is '/foo'
|
||||
const default_path = normalized_url.split('/').slice(0, -1).join('/') || '/';
|
||||
|
||||
/** @type {Record<string, import('./page/types.js').Cookie>} */
|
||||
const new_cookies = {};
|
||||
|
||||
/** @type {import('cookie').CookieSerializeOptions} */
|
||||
const defaults = {
|
||||
httpOnly: true,
|
||||
sameSite: 'lax',
|
||||
secure: url.hostname === 'localhost' && url.protocol === 'http:' ? false : true
|
||||
};
|
||||
|
||||
/** @type {import('@sveltejs/kit').Cookies} */
|
||||
const cookies = {
|
||||
// The JSDoc param annotations appearing below for get, set and delete
|
||||
// are necessary to expose the `cookie` library types to
|
||||
// typescript users. `@type {import('@sveltejs/kit').Cookies}` above is not
|
||||
// sufficient to do so.
|
||||
|
||||
/**
|
||||
* @param {string} name
|
||||
* @param {import('cookie').CookieParseOptions} opts
|
||||
*/
|
||||
get(name, opts) {
|
||||
const c = new_cookies[name];
|
||||
if (
|
||||
c &&
|
||||
domain_matches(url.hostname, c.options.domain) &&
|
||||
path_matches(url.pathname, c.options.path)
|
||||
) {
|
||||
return c.value;
|
||||
}
|
||||
|
||||
const decoder = opts?.decode || decodeURIComponent;
|
||||
const req_cookies = parse(header, { decode: decoder });
|
||||
const cookie = req_cookies[name]; // the decoded string or undefined
|
||||
|
||||
// in development, if the cookie was set during this session with `cookies.set`,
|
||||
// but at a different path, warn the user. (ignore cookies from request headers,
|
||||
// since we don't know which path they were set at)
|
||||
if (__SVELTEKIT_DEV__ && !cookie) {
|
||||
const paths = Array.from(cookie_paths[name] ?? []).filter((path) => {
|
||||
// we only care about paths that are _more_ specific than the current path
|
||||
return path_matches(path, url.pathname) && path !== url.pathname;
|
||||
});
|
||||
|
||||
if (paths.length > 0) {
|
||||
console.warn(
|
||||
// prettier-ignore
|
||||
`'${name}' cookie does not exist for ${url.pathname}, but was previously set at ${conjoin([...paths])}. Did you mean to set its 'path' to '/' instead?`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return cookie;
|
||||
},
|
||||
|
||||
/**
|
||||
* @param {import('cookie').CookieParseOptions} opts
|
||||
*/
|
||||
getAll(opts) {
|
||||
const decoder = opts?.decode || decodeURIComponent;
|
||||
const cookies = parse(header, { decode: decoder });
|
||||
|
||||
for (const c of Object.values(new_cookies)) {
|
||||
if (
|
||||
domain_matches(url.hostname, c.options.domain) &&
|
||||
path_matches(url.pathname, c.options.path)
|
||||
) {
|
||||
cookies[c.name] = c.value;
|
||||
}
|
||||
}
|
||||
|
||||
return Object.entries(cookies).map(([name, value]) => ({ name, value }));
|
||||
},
|
||||
|
||||
/**
|
||||
* @param {string} name
|
||||
* @param {string} value
|
||||
* @param {import('cookie').CookieSerializeOptions} opts
|
||||
*/
|
||||
set(name, value, opts = {}) {
|
||||
set_internal(name, value, { ...defaults, ...opts });
|
||||
},
|
||||
|
||||
/**
|
||||
* @param {string} name
|
||||
* @param {import('cookie').CookieSerializeOptions} opts
|
||||
*/
|
||||
delete(name, opts = {}) {
|
||||
cookies.set(name, '', {
|
||||
...opts,
|
||||
maxAge: 0
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* @param {string} name
|
||||
* @param {string} value
|
||||
* @param {import('cookie').CookieSerializeOptions} opts
|
||||
*/
|
||||
serialize(name, value, opts) {
|
||||
return serialize(name, value, {
|
||||
...defaults,
|
||||
...opts
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @param {URL} destination
|
||||
* @param {string | null} header
|
||||
*/
|
||||
function get_cookie_header(destination, header) {
|
||||
/** @type {Record<string, string>} */
|
||||
const combined_cookies = {
|
||||
// cookies sent by the user agent have lowest precedence
|
||||
...initial_cookies
|
||||
};
|
||||
|
||||
// cookies previous set during this event with cookies.set have higher precedence
|
||||
for (const key in new_cookies) {
|
||||
const cookie = new_cookies[key];
|
||||
if (!domain_matches(destination.hostname, cookie.options.domain)) continue;
|
||||
if (!path_matches(destination.pathname, cookie.options.path)) continue;
|
||||
|
||||
const encoder = cookie.options.encode || encodeURIComponent;
|
||||
combined_cookies[cookie.name] = encoder(cookie.value);
|
||||
}
|
||||
|
||||
// explicit header has highest precedence
|
||||
if (header) {
|
||||
const parsed = parse(header, { decode: (value) => value });
|
||||
for (const name in parsed) {
|
||||
combined_cookies[name] = parsed[name];
|
||||
}
|
||||
}
|
||||
|
||||
return Object.entries(combined_cookies)
|
||||
.map(([name, value]) => `${name}=${value}`)
|
||||
.join('; ');
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} name
|
||||
* @param {string} value
|
||||
* @param {import('cookie').CookieSerializeOptions} opts
|
||||
*/
|
||||
function set_internal(name, value, opts) {
|
||||
const path = opts.path ?? default_path;
|
||||
|
||||
new_cookies[name] = {
|
||||
name,
|
||||
value,
|
||||
options: {
|
||||
...opts,
|
||||
path
|
||||
}
|
||||
};
|
||||
|
||||
if (__SVELTEKIT_DEV__) {
|
||||
const serialized = serialize(name, value, new_cookies[name].options);
|
||||
if (new TextEncoder().encode(serialized).byteLength > MAX_COOKIE_SIZE) {
|
||||
throw new Error(`Cookie "${name}" is too large, and will be discarded by the browser`);
|
||||
}
|
||||
|
||||
cookie_paths[name] ??= new Set();
|
||||
|
||||
if (!value) {
|
||||
cookie_paths[name].delete(path);
|
||||
} else {
|
||||
cookie_paths[name].add(path);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return { cookies, new_cookies, get_cookie_header, set_internal };
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} hostname
|
||||
* @param {string} [constraint]
|
||||
*/
|
||||
export function domain_matches(hostname, constraint) {
|
||||
if (!constraint) return true;
|
||||
|
||||
const normalized = constraint[0] === '.' ? constraint.slice(1) : constraint;
|
||||
|
||||
if (hostname === normalized) return true;
|
||||
return hostname.endsWith('.' + normalized);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} path
|
||||
* @param {string} [constraint]
|
||||
*/
|
||||
export function path_matches(path, constraint) {
|
||||
if (!constraint) return true;
|
||||
|
||||
const normalized = constraint.endsWith('/') ? constraint.slice(0, -1) : constraint;
|
||||
|
||||
if (path === normalized) return true;
|
||||
return path.startsWith(normalized + '/');
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Headers} headers
|
||||
* @param {import('./page/types.js').Cookie[]} cookies
|
||||
*/
|
||||
export function add_cookies_to_headers(headers, cookies) {
|
||||
for (const new_cookie of cookies) {
|
||||
const { name, value, options } = new_cookie;
|
||||
headers.append('set-cookie', serialize(name, value, options));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string[]} array
|
||||
*/
|
||||
function conjoin(array) {
|
||||
if (array.length <= 2) return array.join(' and ');
|
||||
return `${array.slice(0, -1).join(', ')} and ${array.at(-1)}`;
|
||||
}
|
263
node_modules/@sveltejs/kit/src/runtime/server/data/index.js
generated
vendored
Normal file
263
node_modules/@sveltejs/kit/src/runtime/server/data/index.js
generated
vendored
Normal file
@ -0,0 +1,263 @@
|
||||
import { HttpError, Redirect } from '../../control.js';
|
||||
import { normalize_error } from '../../../utils/error.js';
|
||||
import { once } from '../../../utils/functions.js';
|
||||
import { load_server_data } from '../page/load_data.js';
|
||||
import { clarify_devalue_error, handle_error_and_jsonify, stringify_uses } from '../utils.js';
|
||||
import { normalize_path } from '../../../utils/url.js';
|
||||
import { text } from '../../../exports/index.js';
|
||||
import * as devalue from 'devalue';
|
||||
import { create_async_iterator } from '../../../utils/streaming.js';
|
||||
|
||||
const encoder = new TextEncoder();
|
||||
|
||||
/**
|
||||
* @param {import('@sveltejs/kit').RequestEvent} event
|
||||
* @param {import('types').SSRRoute} route
|
||||
* @param {import('types').SSROptions} options
|
||||
* @param {import('@sveltejs/kit').SSRManifest} manifest
|
||||
* @param {import('types').SSRState} state
|
||||
* @param {boolean[] | undefined} invalidated_data_nodes
|
||||
* @param {import('types').TrailingSlash} trailing_slash
|
||||
* @returns {Promise<Response>}
|
||||
*/
|
||||
export async function render_data(
|
||||
event,
|
||||
route,
|
||||
options,
|
||||
manifest,
|
||||
state,
|
||||
invalidated_data_nodes,
|
||||
trailing_slash
|
||||
) {
|
||||
if (!route.page) {
|
||||
// requesting /__data.json should fail for a +server.js
|
||||
return new Response(undefined, {
|
||||
status: 404
|
||||
});
|
||||
}
|
||||
|
||||
try {
|
||||
const node_ids = [...route.page.layouts, route.page.leaf];
|
||||
const invalidated = invalidated_data_nodes ?? node_ids.map(() => true);
|
||||
|
||||
let aborted = false;
|
||||
|
||||
const url = new URL(event.url);
|
||||
url.pathname = normalize_path(url.pathname, trailing_slash);
|
||||
|
||||
const new_event = { ...event, url };
|
||||
|
||||
const functions = node_ids.map((n, i) => {
|
||||
return once(async () => {
|
||||
try {
|
||||
if (aborted) {
|
||||
return /** @type {import('types').ServerDataSkippedNode} */ ({
|
||||
type: 'skip'
|
||||
});
|
||||
}
|
||||
|
||||
// == because it could be undefined (in dev) or null (in build, because of JSON.stringify)
|
||||
const node = n == undefined ? n : await manifest._.nodes[n]();
|
||||
// load this. for the child, return as is. for the final result, stream things
|
||||
return load_server_data({
|
||||
event: new_event,
|
||||
state,
|
||||
node,
|
||||
parent: async () => {
|
||||
/** @type {Record<string, any>} */
|
||||
const data = {};
|
||||
for (let j = 0; j < i; j += 1) {
|
||||
const parent = /** @type {import('types').ServerDataNode | null} */ (
|
||||
await functions[j]()
|
||||
);
|
||||
|
||||
if (parent) {
|
||||
Object.assign(data, parent.data);
|
||||
}
|
||||
}
|
||||
return data;
|
||||
},
|
||||
track_server_fetches: options.track_server_fetches
|
||||
});
|
||||
} catch (e) {
|
||||
aborted = true;
|
||||
throw e;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
const promises = functions.map(async (fn, i) => {
|
||||
if (!invalidated[i]) {
|
||||
return /** @type {import('types').ServerDataSkippedNode} */ ({
|
||||
type: 'skip'
|
||||
});
|
||||
}
|
||||
|
||||
return fn();
|
||||
});
|
||||
|
||||
let length = promises.length;
|
||||
const nodes = await Promise.all(
|
||||
promises.map((p, i) =>
|
||||
p.catch(async (error) => {
|
||||
if (error instanceof Redirect) {
|
||||
throw error;
|
||||
}
|
||||
|
||||
// Math.min because array isn't guaranteed to resolve in order
|
||||
length = Math.min(length, i + 1);
|
||||
|
||||
return /** @type {import('types').ServerErrorNode} */ ({
|
||||
type: 'error',
|
||||
error: await handle_error_and_jsonify(event, options, error),
|
||||
status: error instanceof HttpError ? error.status : undefined
|
||||
});
|
||||
})
|
||||
)
|
||||
);
|
||||
|
||||
const { data, chunks } = get_data_json(event, options, nodes);
|
||||
|
||||
if (!chunks) {
|
||||
// use a normal JSON response where possible, so we get `content-length`
|
||||
// and can use browser JSON devtools for easier inspecting
|
||||
return json_response(data);
|
||||
}
|
||||
|
||||
return new Response(
|
||||
new ReadableStream({
|
||||
async start(controller) {
|
||||
controller.enqueue(encoder.encode(data));
|
||||
for await (const chunk of chunks) {
|
||||
controller.enqueue(encoder.encode(chunk));
|
||||
}
|
||||
controller.close();
|
||||
},
|
||||
|
||||
type: 'bytes'
|
||||
}),
|
||||
{
|
||||
headers: {
|
||||
// we use a proprietary content type to prevent buffering.
|
||||
// the `text` prefix makes it inspectable
|
||||
'content-type': 'text/sveltekit-data',
|
||||
'cache-control': 'private, no-store'
|
||||
}
|
||||
}
|
||||
);
|
||||
} catch (e) {
|
||||
const error = normalize_error(e);
|
||||
|
||||
if (error instanceof Redirect) {
|
||||
return redirect_json_response(error);
|
||||
} else {
|
||||
return json_response(await handle_error_and_jsonify(event, options, error), 500);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Record<string, any> | string} json
|
||||
* @param {number} [status]
|
||||
*/
|
||||
function json_response(json, status = 200) {
|
||||
return text(typeof json === 'string' ? json : JSON.stringify(json), {
|
||||
status,
|
||||
headers: {
|
||||
'content-type': 'application/json',
|
||||
'cache-control': 'private, no-store'
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Redirect} redirect
|
||||
*/
|
||||
export function redirect_json_response(redirect) {
|
||||
return json_response({
|
||||
type: 'redirect',
|
||||
location: redirect.location
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* If the serialized data contains promises, `chunks` will be an
|
||||
* async iterable containing their resolutions
|
||||
* @param {import('@sveltejs/kit').RequestEvent} event
|
||||
* @param {import('types').SSROptions} options
|
||||
* @param {Array<import('types').ServerDataSkippedNode | import('types').ServerDataNode | import('types').ServerErrorNode | null | undefined>} nodes
|
||||
* @returns {{ data: string, chunks: AsyncIterable<string> | null }}
|
||||
*/
|
||||
export function get_data_json(event, options, nodes) {
|
||||
let promise_id = 1;
|
||||
let count = 0;
|
||||
|
||||
const { iterator, push, done } = create_async_iterator();
|
||||
|
||||
const reducers = {
|
||||
/** @param {any} thing */
|
||||
Promise: (thing) => {
|
||||
if (typeof thing?.then === 'function') {
|
||||
const id = promise_id++;
|
||||
count += 1;
|
||||
|
||||
/** @type {'data' | 'error'} */
|
||||
let key = 'data';
|
||||
|
||||
thing
|
||||
.catch(
|
||||
/** @param {any} e */ async (e) => {
|
||||
key = 'error';
|
||||
return handle_error_and_jsonify(event, options, /** @type {any} */ (e));
|
||||
}
|
||||
)
|
||||
.then(
|
||||
/** @param {any} value */
|
||||
async (value) => {
|
||||
let str;
|
||||
try {
|
||||
str = devalue.stringify(value, reducers);
|
||||
} catch (e) {
|
||||
const error = await handle_error_and_jsonify(
|
||||
event,
|
||||
options,
|
||||
new Error(`Failed to serialize promise while rendering ${event.route.id}`)
|
||||
);
|
||||
|
||||
key = 'error';
|
||||
str = devalue.stringify(error, reducers);
|
||||
}
|
||||
|
||||
count -= 1;
|
||||
|
||||
push(`{"type":"chunk","id":${id},"${key}":${str}}\n`);
|
||||
if (count === 0) done();
|
||||
}
|
||||
);
|
||||
|
||||
return id;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
try {
|
||||
const strings = nodes.map((node) => {
|
||||
if (!node) return 'null';
|
||||
|
||||
if (node.type === 'error' || node.type === 'skip') {
|
||||
return JSON.stringify(node);
|
||||
}
|
||||
|
||||
return `{"type":"data","data":${devalue.stringify(node.data, reducers)},${stringify_uses(
|
||||
node
|
||||
)}${node.slash ? `,"slash":${JSON.stringify(node.slash)}` : ''}}`;
|
||||
});
|
||||
|
||||
return {
|
||||
data: `{"type":"data","nodes":[${strings.join(',')}]}\n`,
|
||||
chunks: count > 0 ? iterator : null
|
||||
};
|
||||
} catch (e) {
|
||||
throw new Error(clarify_devalue_error(event, /** @type {any} */ (e)));
|
||||
}
|
||||
}
|
94
node_modules/@sveltejs/kit/src/runtime/server/endpoint.js
generated
vendored
Normal file
94
node_modules/@sveltejs/kit/src/runtime/server/endpoint.js
generated
vendored
Normal file
@ -0,0 +1,94 @@
|
||||
import { ENDPOINT_METHODS, PAGE_METHODS } from '../../constants.js';
|
||||
import { negotiate } from '../../utils/http.js';
|
||||
import { Redirect } from '../control.js';
|
||||
import { method_not_allowed } from './utils.js';
|
||||
|
||||
/**
|
||||
* @param {import('@sveltejs/kit').RequestEvent} event
|
||||
* @param {import('types').SSREndpoint} mod
|
||||
* @param {import('types').SSRState} state
|
||||
* @returns {Promise<Response>}
|
||||
*/
|
||||
export async function render_endpoint(event, mod, state) {
|
||||
const method = /** @type {import('types').HttpMethod} */ (event.request.method);
|
||||
|
||||
let handler = mod[method] || mod.fallback;
|
||||
|
||||
if (method === 'HEAD' && mod.GET && !mod.HEAD) {
|
||||
handler = mod.GET;
|
||||
}
|
||||
|
||||
if (!handler) {
|
||||
return method_not_allowed(mod, method);
|
||||
}
|
||||
|
||||
const prerender = mod.prerender ?? state.prerender_default;
|
||||
|
||||
if (prerender && (mod.POST || mod.PATCH || mod.PUT || mod.DELETE)) {
|
||||
throw new Error('Cannot prerender endpoints that have mutative methods');
|
||||
}
|
||||
|
||||
if (state.prerendering && !prerender) {
|
||||
if (state.depth > 0) {
|
||||
// if request came from a prerendered page, bail
|
||||
throw new Error(`${event.route.id} is not prerenderable`);
|
||||
} else {
|
||||
// if request came direct from the crawler, signal that
|
||||
// this route cannot be prerendered, but don't bail
|
||||
return new Response(undefined, { status: 204 });
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
let response = await handler(
|
||||
/** @type {import('@sveltejs/kit').RequestEvent<Record<string, any>>} */ (event)
|
||||
);
|
||||
|
||||
if (!(response instanceof Response)) {
|
||||
throw new Error(
|
||||
`Invalid response from route ${event.url.pathname}: handler should return a Response object`
|
||||
);
|
||||
}
|
||||
|
||||
if (state.prerendering) {
|
||||
// the returned Response might have immutable Headers
|
||||
// so we should 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.set('x-sveltekit-prerender', String(prerender));
|
||||
}
|
||||
|
||||
return response;
|
||||
} catch (e) {
|
||||
if (e instanceof Redirect) {
|
||||
return new Response(undefined, {
|
||||
status: e.status,
|
||||
headers: { location: e.location }
|
||||
});
|
||||
}
|
||||
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {import('@sveltejs/kit').RequestEvent} event
|
||||
*/
|
||||
export function is_endpoint_request(event) {
|
||||
const { method, headers } = event.request;
|
||||
|
||||
// These methods exist exclusively for endpoints
|
||||
if (ENDPOINT_METHODS.has(method) && !PAGE_METHODS.has(method)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// use:enhance uses a custom header to disambiguate
|
||||
if (method === 'POST' && headers.get('x-sveltekit-action') === 'true') return false;
|
||||
|
||||
// GET/POST requests may be for endpoints or pages. We prefer endpoints if this isn't a text/html request
|
||||
const accept = event.request.headers.get('accept') ?? '*/*';
|
||||
return negotiate(accept, ['*', 'text/html']) !== 'text/html';
|
||||
}
|
160
node_modules/@sveltejs/kit/src/runtime/server/fetch.js
generated
vendored
Normal file
160
node_modules/@sveltejs/kit/src/runtime/server/fetch.js
generated
vendored
Normal file
@ -0,0 +1,160 @@
|
||||
import * as set_cookie_parser from 'set-cookie-parser';
|
||||
import { respond } from './respond.js';
|
||||
import * as paths from '__sveltekit/paths';
|
||||
|
||||
/**
|
||||
* @param {{
|
||||
* event: import('@sveltejs/kit').RequestEvent;
|
||||
* options: import('types').SSROptions;
|
||||
* manifest: import('@sveltejs/kit').SSRManifest;
|
||||
* state: import('types').SSRState;
|
||||
* get_cookie_header: (url: URL, header: string | null) => string;
|
||||
* set_internal: (name: string, value: string, opts: import('cookie').CookieSerializeOptions) => void;
|
||||
* }} opts
|
||||
* @returns {typeof fetch}
|
||||
*/
|
||||
export function create_fetch({ event, options, manifest, state, get_cookie_header, set_internal }) {
|
||||
return async (info, init) => {
|
||||
const original_request = normalize_fetch_input(info, init, event.url);
|
||||
|
||||
// some runtimes (e.g. Cloudflare) error if you access `request.mode`,
|
||||
// annoyingly, so we need to read the value from the `init` object instead
|
||||
let mode = (info instanceof Request ? info.mode : init?.mode) ?? 'cors';
|
||||
let credentials =
|
||||
(info instanceof Request ? info.credentials : init?.credentials) ?? 'same-origin';
|
||||
|
||||
return await options.hooks.handleFetch({
|
||||
event,
|
||||
request: original_request,
|
||||
fetch: async (info, init) => {
|
||||
const request = normalize_fetch_input(info, init, event.url);
|
||||
|
||||
const url = new URL(request.url);
|
||||
|
||||
if (!request.headers.has('origin')) {
|
||||
request.headers.set('origin', event.url.origin);
|
||||
}
|
||||
|
||||
if (info !== original_request) {
|
||||
mode = (info instanceof Request ? info.mode : init?.mode) ?? 'cors';
|
||||
credentials =
|
||||
(info instanceof Request ? info.credentials : init?.credentials) ?? 'same-origin';
|
||||
}
|
||||
|
||||
// Remove Origin, according to https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Origin#description
|
||||
if (
|
||||
(request.method === 'GET' || request.method === 'HEAD') &&
|
||||
((mode === 'no-cors' && url.origin !== event.url.origin) ||
|
||||
url.origin === event.url.origin)
|
||||
) {
|
||||
request.headers.delete('origin');
|
||||
}
|
||||
|
||||
if (url.origin !== event.url.origin) {
|
||||
// Allow cookie passthrough for "credentials: same-origin" and "credentials: include"
|
||||
// if SvelteKit is serving my.domain.com:
|
||||
// - domain.com WILL NOT receive cookies
|
||||
// - my.domain.com WILL receive cookies
|
||||
// - api.domain.dom WILL NOT receive cookies
|
||||
// - sub.my.domain.com WILL receive cookies
|
||||
// ports do not affect the resolution
|
||||
// leading dot prevents mydomain.com matching domain.com
|
||||
// Do not forward other cookies for "credentials: include" because we don't know
|
||||
// which cookie belongs to which domain (browser does not pass this info)
|
||||
if (`.${url.hostname}`.endsWith(`.${event.url.hostname}`) && credentials !== 'omit') {
|
||||
const cookie = get_cookie_header(url, request.headers.get('cookie'));
|
||||
if (cookie) request.headers.set('cookie', cookie);
|
||||
}
|
||||
|
||||
return fetch(request);
|
||||
}
|
||||
|
||||
// handle fetch requests for static assets. e.g. prebaked data, etc.
|
||||
// we need to support everything the browser's fetch supports
|
||||
const prefix = paths.assets || paths.base;
|
||||
const decoded = decodeURIComponent(url.pathname);
|
||||
const filename = (
|
||||
decoded.startsWith(prefix) ? decoded.slice(prefix.length) : decoded
|
||||
).slice(1);
|
||||
const filename_html = `${filename}/index.html`; // path may also match path/index.html
|
||||
|
||||
const is_asset = manifest.assets.has(filename);
|
||||
const is_asset_html = manifest.assets.has(filename_html);
|
||||
|
||||
if (is_asset || is_asset_html) {
|
||||
const file = is_asset ? filename : filename_html;
|
||||
|
||||
if (state.read) {
|
||||
const type = is_asset
|
||||
? manifest.mimeTypes[filename.slice(filename.lastIndexOf('.'))]
|
||||
: 'text/html';
|
||||
|
||||
return new Response(state.read(file), {
|
||||
headers: type ? { 'content-type': type } : {}
|
||||
});
|
||||
}
|
||||
|
||||
return await fetch(request);
|
||||
}
|
||||
|
||||
if (credentials !== 'omit') {
|
||||
const cookie = get_cookie_header(url, request.headers.get('cookie'));
|
||||
if (cookie) {
|
||||
request.headers.set('cookie', cookie);
|
||||
}
|
||||
|
||||
const authorization = event.request.headers.get('authorization');
|
||||
if (authorization && !request.headers.has('authorization')) {
|
||||
request.headers.set('authorization', authorization);
|
||||
}
|
||||
}
|
||||
|
||||
if (!request.headers.has('accept')) {
|
||||
request.headers.set('accept', '*/*');
|
||||
}
|
||||
|
||||
if (!request.headers.has('accept-language')) {
|
||||
request.headers.set(
|
||||
'accept-language',
|
||||
/** @type {string} */ (event.request.headers.get('accept-language'))
|
||||
);
|
||||
}
|
||||
|
||||
/** @type {Response} */
|
||||
const response = await respond(request, options, manifest, {
|
||||
...state,
|
||||
depth: state.depth + 1
|
||||
});
|
||||
|
||||
const set_cookie = response.headers.get('set-cookie');
|
||||
if (set_cookie) {
|
||||
for (const str of set_cookie_parser.splitCookiesString(set_cookie)) {
|
||||
const { name, value, ...options } = set_cookie_parser.parseString(str);
|
||||
|
||||
// options.sameSite is string, something more specific is required - type cast is safe
|
||||
set_internal(
|
||||
name,
|
||||
value,
|
||||
/** @type {import('cookie').CookieSerializeOptions} */ (options)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return response;
|
||||
}
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {RequestInfo | URL} info
|
||||
* @param {RequestInit | undefined} init
|
||||
* @param {URL} url
|
||||
*/
|
||||
function normalize_fetch_input(info, init, url) {
|
||||
if (info instanceof Request) {
|
||||
return info;
|
||||
}
|
||||
|
||||
return new Request(typeof info === 'string' ? new URL(info, url) : info, init);
|
||||
}
|
87
node_modules/@sveltejs/kit/src/runtime/server/index.js
generated
vendored
Normal file
87
node_modules/@sveltejs/kit/src/runtime/server/index.js
generated
vendored
Normal file
@ -0,0 +1,87 @@
|
||||
import { respond } from './respond.js';
|
||||
import { set_private_env, set_public_env } from '../shared-server.js';
|
||||
import { options, get_hooks } from '__SERVER__/internal.js';
|
||||
import { DEV } from 'esm-env';
|
||||
import { filter_private_env, filter_public_env } from '../../utils/env.js';
|
||||
|
||||
export class Server {
|
||||
/** @type {import('types').SSROptions} */
|
||||
#options;
|
||||
|
||||
/** @type {import('@sveltejs/kit').SSRManifest} */
|
||||
#manifest;
|
||||
|
||||
/** @param {import('@sveltejs/kit').SSRManifest} manifest */
|
||||
constructor(manifest) {
|
||||
/** @type {import('types').SSROptions} */
|
||||
this.#options = options;
|
||||
this.#manifest = manifest;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {{
|
||||
* env: Record<string, string>
|
||||
* }} opts
|
||||
*/
|
||||
async init({ env }) {
|
||||
// Take care: Some adapters may have to call `Server.init` per-request to set env vars,
|
||||
// so anything that shouldn't be rerun should be wrapped in an `if` block to make sure it hasn't
|
||||
// been done already.
|
||||
// set env, in case it's used in initialisation
|
||||
set_private_env(
|
||||
filter_private_env(env, {
|
||||
public_prefix: this.#options.env_public_prefix,
|
||||
private_prefix: this.#options.env_private_prefix
|
||||
})
|
||||
);
|
||||
set_public_env(
|
||||
filter_public_env(env, {
|
||||
public_prefix: this.#options.env_public_prefix,
|
||||
private_prefix: this.#options.env_private_prefix
|
||||
})
|
||||
);
|
||||
|
||||
if (!this.#options.hooks) {
|
||||
try {
|
||||
const module = await get_hooks();
|
||||
|
||||
this.#options.hooks = {
|
||||
handle: module.handle || (({ event, resolve }) => resolve(event)),
|
||||
handleError: module.handleError || (({ error }) => console.error(error)),
|
||||
handleFetch: module.handleFetch || (({ request, fetch }) => fetch(request))
|
||||
};
|
||||
} catch (error) {
|
||||
if (DEV) {
|
||||
this.#options.hooks = {
|
||||
handle: () => {
|
||||
throw error;
|
||||
},
|
||||
handleError: ({ error }) => console.error(error),
|
||||
handleFetch: ({ request, fetch }) => fetch(request)
|
||||
};
|
||||
} else {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Request} request
|
||||
* @param {import('types').RequestOptions} options
|
||||
*/
|
||||
async respond(request, options) {
|
||||
// TODO this should probably have been removed for 1.0 — i think we can get rid of it?
|
||||
if (!(request instanceof Request)) {
|
||||
throw new Error(
|
||||
'The first argument to server.respond must be a Request object. See https://github.com/sveltejs/kit/pull/3384 for details'
|
||||
);
|
||||
}
|
||||
|
||||
return respond(request, this.#options, this.#manifest, {
|
||||
...options,
|
||||
error: false,
|
||||
depth: 0
|
||||
});
|
||||
}
|
||||
}
|
282
node_modules/@sveltejs/kit/src/runtime/server/page/actions.js
generated
vendored
Normal file
282
node_modules/@sveltejs/kit/src/runtime/server/page/actions.js
generated
vendored
Normal file
@ -0,0 +1,282 @@
|
||||
import * as devalue from 'devalue';
|
||||
import { error, json } from '../../../exports/index.js';
|
||||
import { normalize_error } from '../../../utils/error.js';
|
||||
import { is_form_content_type, negotiate } from '../../../utils/http.js';
|
||||
import { HttpError, Redirect, ActionFailure } from '../../control.js';
|
||||
import { handle_error_and_jsonify } from '../utils.js';
|
||||
|
||||
/** @param {import('@sveltejs/kit').RequestEvent} event */
|
||||
export function is_action_json_request(event) {
|
||||
const accept = negotiate(event.request.headers.get('accept') ?? '*/*', [
|
||||
'application/json',
|
||||
'text/html'
|
||||
]);
|
||||
|
||||
return accept === 'application/json' && event.request.method === 'POST';
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {import('@sveltejs/kit').RequestEvent} event
|
||||
* @param {import('types').SSROptions} options
|
||||
* @param {import('types').SSRNode['server'] | undefined} server
|
||||
*/
|
||||
export async function handle_action_json_request(event, options, server) {
|
||||
const actions = server?.actions;
|
||||
|
||||
if (!actions) {
|
||||
// TODO should this be a different error altogether?
|
||||
const no_actions_error = error(405, 'POST method not allowed. No actions exist for this page');
|
||||
return action_json(
|
||||
{
|
||||
type: 'error',
|
||||
error: await handle_error_and_jsonify(event, options, no_actions_error)
|
||||
},
|
||||
{
|
||||
status: no_actions_error.status,
|
||||
headers: {
|
||||
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/405
|
||||
// "The server must generate an Allow header field in a 405 status code response"
|
||||
allow: 'GET'
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
check_named_default_separate(actions);
|
||||
|
||||
try {
|
||||
const data = await call_action(event, actions);
|
||||
|
||||
if (__SVELTEKIT_DEV__) {
|
||||
validate_action_return(data);
|
||||
}
|
||||
|
||||
if (data instanceof ActionFailure) {
|
||||
return action_json({
|
||||
type: 'failure',
|
||||
status: data.status,
|
||||
// @ts-expect-error we assign a string to what is supposed to be an object. That's ok
|
||||
// because we don't use the object outside, and this way we have better code navigation
|
||||
// through knowing where the related interface is used.
|
||||
data: stringify_action_response(data.data, /** @type {string} */ (event.route.id))
|
||||
});
|
||||
} else {
|
||||
return action_json({
|
||||
type: 'success',
|
||||
status: data ? 200 : 204,
|
||||
// @ts-expect-error see comment above
|
||||
data: stringify_action_response(data, /** @type {string} */ (event.route.id))
|
||||
});
|
||||
}
|
||||
} catch (e) {
|
||||
const err = normalize_error(e);
|
||||
|
||||
if (err instanceof Redirect) {
|
||||
return action_json_redirect(err);
|
||||
}
|
||||
|
||||
return action_json(
|
||||
{
|
||||
type: 'error',
|
||||
error: await handle_error_and_jsonify(event, options, check_incorrect_fail_use(err))
|
||||
},
|
||||
{
|
||||
status: err instanceof HttpError ? err.status : 500
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {HttpError | Error} error
|
||||
*/
|
||||
function check_incorrect_fail_use(error) {
|
||||
return error instanceof ActionFailure
|
||||
? new Error('Cannot "throw fail()". Use "return fail()"')
|
||||
: error;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {import('@sveltejs/kit').Redirect} redirect
|
||||
*/
|
||||
export function action_json_redirect(redirect) {
|
||||
return action_json({
|
||||
type: 'redirect',
|
||||
status: redirect.status,
|
||||
location: redirect.location
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {import('@sveltejs/kit').ActionResult} data
|
||||
* @param {ResponseInit} [init]
|
||||
*/
|
||||
function action_json(data, init) {
|
||||
return json(data, init);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {import('@sveltejs/kit').RequestEvent} event
|
||||
*/
|
||||
export function is_action_request(event) {
|
||||
return event.request.method === 'POST';
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {import('@sveltejs/kit').RequestEvent} event
|
||||
* @param {import('types').SSRNode['server'] | undefined} server
|
||||
* @returns {Promise<import('@sveltejs/kit').ActionResult>}
|
||||
*/
|
||||
export async function handle_action_request(event, server) {
|
||||
const actions = server?.actions;
|
||||
|
||||
if (!actions) {
|
||||
// TODO should this be a different error altogether?
|
||||
event.setHeaders({
|
||||
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/405
|
||||
// "The server must generate an Allow header field in a 405 status code response"
|
||||
allow: 'GET'
|
||||
});
|
||||
return {
|
||||
type: 'error',
|
||||
error: error(405, 'POST method not allowed. No actions exist for this page')
|
||||
};
|
||||
}
|
||||
|
||||
check_named_default_separate(actions);
|
||||
|
||||
try {
|
||||
const data = await call_action(event, actions);
|
||||
|
||||
if (__SVELTEKIT_DEV__) {
|
||||
validate_action_return(data);
|
||||
}
|
||||
|
||||
if (data instanceof ActionFailure) {
|
||||
return {
|
||||
type: 'failure',
|
||||
status: data.status,
|
||||
data: data.data
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
type: 'success',
|
||||
status: 200,
|
||||
// @ts-expect-error this will be removed upon serialization, so `undefined` is the same as omission
|
||||
data
|
||||
};
|
||||
}
|
||||
} catch (e) {
|
||||
const err = normalize_error(e);
|
||||
|
||||
if (err instanceof Redirect) {
|
||||
return {
|
||||
type: 'redirect',
|
||||
status: err.status,
|
||||
location: err.location
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
type: 'error',
|
||||
error: check_incorrect_fail_use(err)
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {import('@sveltejs/kit').Actions} actions
|
||||
*/
|
||||
function check_named_default_separate(actions) {
|
||||
if (actions.default && Object.keys(actions).length > 1) {
|
||||
throw new Error(
|
||||
'When using named actions, the default action cannot be used. See the docs for more info: https://kit.svelte.dev/docs/form-actions#named-actions'
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {import('@sveltejs/kit').RequestEvent} event
|
||||
* @param {NonNullable<import('types').SSRNode['server']['actions']>} actions
|
||||
* @throws {Redirect | ActionFailure | HttpError | Error}
|
||||
*/
|
||||
async function call_action(event, actions) {
|
||||
const url = new URL(event.request.url);
|
||||
|
||||
let name = 'default';
|
||||
for (const param of url.searchParams) {
|
||||
if (param[0].startsWith('/')) {
|
||||
name = param[0].slice(1);
|
||||
if (name === 'default') {
|
||||
throw new Error('Cannot use reserved action name "default"');
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
const action = actions[name];
|
||||
if (!action) {
|
||||
throw new Error(`No action with name '${name}' found`);
|
||||
}
|
||||
|
||||
if (!is_form_content_type(event.request)) {
|
||||
throw new Error(
|
||||
`Actions expect form-encoded data (received ${event.request.headers.get('content-type')})`
|
||||
);
|
||||
}
|
||||
|
||||
return action(event);
|
||||
}
|
||||
|
||||
/** @param {any} data */
|
||||
function validate_action_return(data) {
|
||||
if (data instanceof Redirect) {
|
||||
throw new Error('Cannot `return redirect(...)` — use `throw redirect(...)` instead');
|
||||
}
|
||||
|
||||
if (data instanceof HttpError) {
|
||||
throw new Error(
|
||||
'Cannot `return error(...)` — use `throw error(...)` or `return fail(...)` instead'
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Try to `devalue.uneval` the data object, and if it fails, return a proper Error with context
|
||||
* @param {any} data
|
||||
* @param {string} route_id
|
||||
*/
|
||||
export function uneval_action_response(data, route_id) {
|
||||
return try_deserialize(data, devalue.uneval, route_id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Try to `devalue.stringify` the data object, and if it fails, return a proper Error with context
|
||||
* @param {any} data
|
||||
* @param {string} route_id
|
||||
*/
|
||||
function stringify_action_response(data, route_id) {
|
||||
return try_deserialize(data, devalue.stringify, route_id);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {any} data
|
||||
* @param {(data: any) => string} fn
|
||||
* @param {string} route_id
|
||||
*/
|
||||
function try_deserialize(data, fn, route_id) {
|
||||
try {
|
||||
return fn(data);
|
||||
} catch (e) {
|
||||
// If we're here, the data could not be serialized with devalue
|
||||
const error = /** @type {any} */ (e);
|
||||
|
||||
if ('path' in error) {
|
||||
let message = `Data returned from action inside ${route_id} is not serializable: ${error.message}`;
|
||||
if (error.path !== '') message += ` (data.${error.path})`;
|
||||
throw new Error(message);
|
||||
}
|
||||
|
||||
throw error;
|
||||
}
|
||||
}
|
239
node_modules/@sveltejs/kit/src/runtime/server/page/crypto.js
generated
vendored
Normal file
239
node_modules/@sveltejs/kit/src/runtime/server/page/crypto.js
generated
vendored
Normal file
@ -0,0 +1,239 @@
|
||||
const encoder = new TextEncoder();
|
||||
|
||||
/**
|
||||
* SHA-256 hashing function adapted from https://bitwiseshiftleft.github.io/sjcl
|
||||
* modified and redistributed under BSD license
|
||||
* @param {string} data
|
||||
*/
|
||||
export function sha256(data) {
|
||||
if (!key[0]) precompute();
|
||||
|
||||
const out = init.slice(0);
|
||||
const array = encode(data);
|
||||
|
||||
for (let i = 0; i < array.length; i += 16) {
|
||||
const w = array.subarray(i, i + 16);
|
||||
|
||||
let tmp;
|
||||
let a;
|
||||
let b;
|
||||
|
||||
let out0 = out[0];
|
||||
let out1 = out[1];
|
||||
let out2 = out[2];
|
||||
let out3 = out[3];
|
||||
let out4 = out[4];
|
||||
let out5 = out[5];
|
||||
let out6 = out[6];
|
||||
let out7 = out[7];
|
||||
|
||||
/* Rationale for placement of |0 :
|
||||
* If a value can overflow is original 32 bits by a factor of more than a few
|
||||
* million (2^23 ish), there is a possibility that it might overflow the
|
||||
* 53-bit mantissa and lose precision.
|
||||
*
|
||||
* To avoid this, we clamp back to 32 bits by |'ing with 0 on any value that
|
||||
* propagates around the loop, and on the hash state out[]. I don't believe
|
||||
* that the clamps on out4 and on out0 are strictly necessary, but it's close
|
||||
* (for out4 anyway), and better safe than sorry.
|
||||
*
|
||||
* The clamps on out[] are necessary for the output to be correct even in the
|
||||
* common case and for short inputs.
|
||||
*/
|
||||
|
||||
for (let i = 0; i < 64; i++) {
|
||||
// load up the input word for this round
|
||||
|
||||
if (i < 16) {
|
||||
tmp = w[i];
|
||||
} else {
|
||||
a = w[(i + 1) & 15];
|
||||
|
||||
b = w[(i + 14) & 15];
|
||||
|
||||
tmp = w[i & 15] =
|
||||
(((a >>> 7) ^ (a >>> 18) ^ (a >>> 3) ^ (a << 25) ^ (a << 14)) +
|
||||
((b >>> 17) ^ (b >>> 19) ^ (b >>> 10) ^ (b << 15) ^ (b << 13)) +
|
||||
w[i & 15] +
|
||||
w[(i + 9) & 15]) |
|
||||
0;
|
||||
}
|
||||
|
||||
tmp =
|
||||
tmp +
|
||||
out7 +
|
||||
((out4 >>> 6) ^ (out4 >>> 11) ^ (out4 >>> 25) ^ (out4 << 26) ^ (out4 << 21) ^ (out4 << 7)) +
|
||||
(out6 ^ (out4 & (out5 ^ out6))) +
|
||||
key[i]; // | 0;
|
||||
|
||||
// shift register
|
||||
out7 = out6;
|
||||
out6 = out5;
|
||||
out5 = out4;
|
||||
|
||||
out4 = (out3 + tmp) | 0;
|
||||
|
||||
out3 = out2;
|
||||
out2 = out1;
|
||||
out1 = out0;
|
||||
|
||||
out0 =
|
||||
(tmp +
|
||||
((out1 & out2) ^ (out3 & (out1 ^ out2))) +
|
||||
((out1 >>> 2) ^
|
||||
(out1 >>> 13) ^
|
||||
(out1 >>> 22) ^
|
||||
(out1 << 30) ^
|
||||
(out1 << 19) ^
|
||||
(out1 << 10))) |
|
||||
0;
|
||||
}
|
||||
|
||||
out[0] = (out[0] + out0) | 0;
|
||||
out[1] = (out[1] + out1) | 0;
|
||||
out[2] = (out[2] + out2) | 0;
|
||||
out[3] = (out[3] + out3) | 0;
|
||||
out[4] = (out[4] + out4) | 0;
|
||||
out[5] = (out[5] + out5) | 0;
|
||||
out[6] = (out[6] + out6) | 0;
|
||||
out[7] = (out[7] + out7) | 0;
|
||||
}
|
||||
|
||||
const bytes = new Uint8Array(out.buffer);
|
||||
reverse_endianness(bytes);
|
||||
|
||||
return base64(bytes);
|
||||
}
|
||||
|
||||
/** The SHA-256 initialization vector */
|
||||
const init = new Uint32Array(8);
|
||||
|
||||
/** The SHA-256 hash key */
|
||||
const key = new Uint32Array(64);
|
||||
|
||||
/** Function to precompute init and key. */
|
||||
function precompute() {
|
||||
/** @param {number} x */
|
||||
function frac(x) {
|
||||
return (x - Math.floor(x)) * 0x100000000;
|
||||
}
|
||||
|
||||
let prime = 2;
|
||||
|
||||
for (let i = 0; i < 64; prime++) {
|
||||
let is_prime = true;
|
||||
|
||||
for (let factor = 2; factor * factor <= prime; factor++) {
|
||||
if (prime % factor === 0) {
|
||||
is_prime = false;
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (is_prime) {
|
||||
if (i < 8) {
|
||||
init[i] = frac(prime ** (1 / 2));
|
||||
}
|
||||
|
||||
key[i] = frac(prime ** (1 / 3));
|
||||
|
||||
i++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** @param {Uint8Array} bytes */
|
||||
function reverse_endianness(bytes) {
|
||||
for (let i = 0; i < bytes.length; i += 4) {
|
||||
const a = bytes[i + 0];
|
||||
const b = bytes[i + 1];
|
||||
const c = bytes[i + 2];
|
||||
const d = bytes[i + 3];
|
||||
|
||||
bytes[i + 0] = d;
|
||||
bytes[i + 1] = c;
|
||||
bytes[i + 2] = b;
|
||||
bytes[i + 3] = a;
|
||||
}
|
||||
}
|
||||
|
||||
/** @param {string} str */
|
||||
function encode(str) {
|
||||
const encoded = encoder.encode(str);
|
||||
const length = encoded.length * 8;
|
||||
|
||||
// result should be a multiple of 512 bits in length,
|
||||
// with room for a 1 (after the data) and two 32-bit
|
||||
// words containing the original input bit length
|
||||
const size = 512 * Math.ceil((length + 65) / 512);
|
||||
const bytes = new Uint8Array(size / 8);
|
||||
bytes.set(encoded);
|
||||
|
||||
// append a 1
|
||||
bytes[encoded.length] = 0b10000000;
|
||||
|
||||
reverse_endianness(bytes);
|
||||
|
||||
// add the input bit length
|
||||
const words = new Uint32Array(bytes.buffer);
|
||||
words[words.length - 2] = Math.floor(length / 0x100000000); // this will always be zero for us
|
||||
words[words.length - 1] = length;
|
||||
|
||||
return words;
|
||||
}
|
||||
|
||||
/*
|
||||
Based on https://gist.github.com/enepomnyaschih/72c423f727d395eeaa09697058238727
|
||||
|
||||
MIT License
|
||||
Copyright (c) 2020 Egor Nepomnyaschih
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
*/
|
||||
const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'.split('');
|
||||
|
||||
/** @param {Uint8Array} bytes */
|
||||
export function base64(bytes) {
|
||||
const l = bytes.length;
|
||||
|
||||
let result = '';
|
||||
let i;
|
||||
|
||||
for (i = 2; i < l; i += 3) {
|
||||
result += chars[bytes[i - 2] >> 2];
|
||||
result += chars[((bytes[i - 2] & 0x03) << 4) | (bytes[i - 1] >> 4)];
|
||||
result += chars[((bytes[i - 1] & 0x0f) << 2) | (bytes[i] >> 6)];
|
||||
result += chars[bytes[i] & 0x3f];
|
||||
}
|
||||
|
||||
if (i === l + 1) {
|
||||
// 1 octet yet to write
|
||||
result += chars[bytes[i - 2] >> 2];
|
||||
result += chars[(bytes[i - 2] & 0x03) << 4];
|
||||
result += '==';
|
||||
}
|
||||
|
||||
if (i === l) {
|
||||
// 2 octets yet to write
|
||||
result += chars[bytes[i - 2] >> 2];
|
||||
result += chars[((bytes[i - 2] & 0x03) << 4) | (bytes[i - 1] >> 4)];
|
||||
result += chars[(bytes[i - 1] & 0x0f) << 2];
|
||||
result += '=';
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
254
node_modules/@sveltejs/kit/src/runtime/server/page/csp.js
generated
vendored
Normal file
254
node_modules/@sveltejs/kit/src/runtime/server/page/csp.js
generated
vendored
Normal file
@ -0,0 +1,254 @@
|
||||
import { escape_html_attr } from '../../../utils/escape.js';
|
||||
import { base64, sha256 } from './crypto.js';
|
||||
|
||||
const array = new Uint8Array(16);
|
||||
|
||||
function generate_nonce() {
|
||||
crypto.getRandomValues(array);
|
||||
return base64(array);
|
||||
}
|
||||
|
||||
const quoted = new Set([
|
||||
'self',
|
||||
'unsafe-eval',
|
||||
'unsafe-hashes',
|
||||
'unsafe-inline',
|
||||
'none',
|
||||
'strict-dynamic',
|
||||
'report-sample',
|
||||
'wasm-unsafe-eval',
|
||||
'script'
|
||||
]);
|
||||
|
||||
const crypto_pattern = /^(nonce|sha\d\d\d)-/;
|
||||
|
||||
// CSP and CSP Report Only are extremely similar with a few caveats
|
||||
// the easiest/DRYest way to express this is with some private encapsulation
|
||||
class BaseProvider {
|
||||
/** @type {boolean} */
|
||||
#use_hashes;
|
||||
|
||||
/** @type {boolean} */
|
||||
#script_needs_csp;
|
||||
|
||||
/** @type {boolean} */
|
||||
#style_needs_csp;
|
||||
|
||||
/** @type {import('types').CspDirectives} */
|
||||
#directives;
|
||||
|
||||
/** @type {import('types').Csp.Source[]} */
|
||||
#script_src;
|
||||
|
||||
/** @type {import('types').Csp.Source[]} */
|
||||
#style_src;
|
||||
|
||||
/** @type {string} */
|
||||
#nonce;
|
||||
|
||||
/**
|
||||
* @param {boolean} use_hashes
|
||||
* @param {import('types').CspDirectives} directives
|
||||
* @param {string} nonce
|
||||
*/
|
||||
constructor(use_hashes, directives, nonce) {
|
||||
this.#use_hashes = use_hashes;
|
||||
this.#directives = __SVELTEKIT_DEV__ ? { ...directives } : directives; // clone in dev so we can safely mutate
|
||||
|
||||
const d = this.#directives;
|
||||
|
||||
if (__SVELTEKIT_DEV__) {
|
||||
// remove strict-dynamic in dev...
|
||||
// TODO reinstate this if we can figure out how to make strict-dynamic work
|
||||
// if (d['default-src']) {
|
||||
// d['default-src'] = d['default-src'].filter((name) => name !== 'strict-dynamic');
|
||||
// if (d['default-src'].length === 0) delete d['default-src'];
|
||||
// }
|
||||
|
||||
// if (d['script-src']) {
|
||||
// d['script-src'] = d['script-src'].filter((name) => name !== 'strict-dynamic');
|
||||
// if (d['script-src'].length === 0) delete d['script-src'];
|
||||
// }
|
||||
|
||||
const effective_style_src = d['style-src'] || d['default-src'];
|
||||
|
||||
// ...and add unsafe-inline so we can inject <style> elements
|
||||
if (effective_style_src && !effective_style_src.includes('unsafe-inline')) {
|
||||
d['style-src'] = [...effective_style_src, 'unsafe-inline'];
|
||||
}
|
||||
}
|
||||
|
||||
this.#script_src = [];
|
||||
this.#style_src = [];
|
||||
|
||||
const effective_script_src = d['script-src'] || d['default-src'];
|
||||
const effective_style_src = d['style-src'] || d['default-src'];
|
||||
|
||||
this.#script_needs_csp =
|
||||
!!effective_script_src &&
|
||||
effective_script_src.filter((value) => value !== 'unsafe-inline').length > 0;
|
||||
|
||||
this.#style_needs_csp =
|
||||
!__SVELTEKIT_DEV__ &&
|
||||
!!effective_style_src &&
|
||||
effective_style_src.filter((value) => value !== 'unsafe-inline').length > 0;
|
||||
|
||||
this.script_needs_nonce = this.#script_needs_csp && !this.#use_hashes;
|
||||
this.style_needs_nonce = this.#style_needs_csp && !this.#use_hashes;
|
||||
this.#nonce = nonce;
|
||||
}
|
||||
|
||||
/** @param {string} content */
|
||||
add_script(content) {
|
||||
if (this.#script_needs_csp) {
|
||||
if (this.#use_hashes) {
|
||||
this.#script_src.push(`sha256-${sha256(content)}`);
|
||||
} else if (this.#script_src.length === 0) {
|
||||
this.#script_src.push(`nonce-${this.#nonce}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** @param {string} content */
|
||||
add_style(content) {
|
||||
if (this.#style_needs_csp) {
|
||||
if (this.#use_hashes) {
|
||||
this.#style_src.push(`sha256-${sha256(content)}`);
|
||||
} else if (this.#style_src.length === 0) {
|
||||
this.#style_src.push(`nonce-${this.#nonce}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {boolean} [is_meta]
|
||||
*/
|
||||
get_header(is_meta = false) {
|
||||
const header = [];
|
||||
|
||||
// due to browser inconsistencies, we can't append sources to default-src
|
||||
// (specifically, Firefox appears to not ignore nonce-{nonce} directives
|
||||
// on default-src), so we ensure that script-src and style-src exist
|
||||
|
||||
const directives = { ...this.#directives };
|
||||
|
||||
if (this.#style_src.length > 0) {
|
||||
directives['style-src'] = [
|
||||
...(directives['style-src'] || directives['default-src'] || []),
|
||||
...this.#style_src
|
||||
];
|
||||
}
|
||||
|
||||
if (this.#script_src.length > 0) {
|
||||
directives['script-src'] = [
|
||||
...(directives['script-src'] || directives['default-src'] || []),
|
||||
...this.#script_src
|
||||
];
|
||||
}
|
||||
|
||||
for (const key in directives) {
|
||||
if (is_meta && (key === 'frame-ancestors' || key === 'report-uri' || key === 'sandbox')) {
|
||||
// these values cannot be used with a <meta> tag
|
||||
// TODO warn?
|
||||
continue;
|
||||
}
|
||||
|
||||
// @ts-expect-error gimme a break typescript, `key` is obviously a member of internal_directives
|
||||
const value = /** @type {string[] | true} */ (directives[key]);
|
||||
|
||||
if (!value) continue;
|
||||
|
||||
const directive = [key];
|
||||
if (Array.isArray(value)) {
|
||||
value.forEach((value) => {
|
||||
if (quoted.has(value) || crypto_pattern.test(value)) {
|
||||
directive.push(`'${value}'`);
|
||||
} else {
|
||||
directive.push(value);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
header.push(directive.join(' '));
|
||||
}
|
||||
|
||||
return header.join('; ');
|
||||
}
|
||||
}
|
||||
|
||||
class CspProvider extends BaseProvider {
|
||||
get_meta() {
|
||||
const content = this.get_header(true);
|
||||
|
||||
if (!content) {
|
||||
return;
|
||||
}
|
||||
|
||||
return `<meta http-equiv="content-security-policy" content=${escape_html_attr(content)}>`;
|
||||
}
|
||||
}
|
||||
|
||||
class CspReportOnlyProvider extends BaseProvider {
|
||||
/**
|
||||
* @param {boolean} use_hashes
|
||||
* @param {import('types').CspDirectives} directives
|
||||
* @param {string} nonce
|
||||
*/
|
||||
constructor(use_hashes, directives, nonce) {
|
||||
super(use_hashes, directives, nonce);
|
||||
|
||||
if (Object.values(directives).filter((v) => !!v).length > 0) {
|
||||
// If we're generating content-security-policy-report-only,
|
||||
// if there are any directives, we need a report-uri or report-to (or both)
|
||||
// else it's just an expensive noop.
|
||||
const has_report_to = directives['report-to']?.length ?? 0 > 0;
|
||||
const has_report_uri = directives['report-uri']?.length ?? 0 > 0;
|
||||
if (!has_report_to && !has_report_uri) {
|
||||
throw Error(
|
||||
'`content-security-policy-report-only` must be specified with either the `report-to` or `report-uri` directives, or both'
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export class Csp {
|
||||
/** @readonly */
|
||||
nonce = generate_nonce();
|
||||
|
||||
/** @type {CspProvider} */
|
||||
csp_provider;
|
||||
|
||||
/** @type {CspReportOnlyProvider} */
|
||||
report_only_provider;
|
||||
|
||||
/**
|
||||
* @param {import('./types.js').CspConfig} config
|
||||
* @param {import('./types.js').CspOpts} opts
|
||||
*/
|
||||
constructor({ mode, directives, reportOnly }, { prerender }) {
|
||||
const use_hashes = mode === 'hash' || (mode === 'auto' && prerender);
|
||||
this.csp_provider = new CspProvider(use_hashes, directives, this.nonce);
|
||||
this.report_only_provider = new CspReportOnlyProvider(use_hashes, reportOnly, this.nonce);
|
||||
}
|
||||
|
||||
get script_needs_nonce() {
|
||||
return this.csp_provider.script_needs_nonce || this.report_only_provider.script_needs_nonce;
|
||||
}
|
||||
|
||||
get style_needs_nonce() {
|
||||
return this.csp_provider.style_needs_nonce || this.report_only_provider.style_needs_nonce;
|
||||
}
|
||||
|
||||
/** @param {string} content */
|
||||
add_script(content) {
|
||||
this.csp_provider.add_script(content);
|
||||
this.report_only_provider.add_script(content);
|
||||
}
|
||||
|
||||
/** @param {string} content */
|
||||
add_style(content) {
|
||||
this.csp_provider.add_style(content);
|
||||
this.report_only_provider.add_style(content);
|
||||
}
|
||||
}
|
315
node_modules/@sveltejs/kit/src/runtime/server/page/index.js
generated
vendored
Normal file
315
node_modules/@sveltejs/kit/src/runtime/server/page/index.js
generated
vendored
Normal file
@ -0,0 +1,315 @@
|
||||
import { text } from '../../../exports/index.js';
|
||||
import { compact } from '../../../utils/array.js';
|
||||
import { normalize_error } from '../../../utils/error.js';
|
||||
import { add_data_suffix } from '../../../utils/url.js';
|
||||
import { HttpError, Redirect } from '../../control.js';
|
||||
import { redirect_response, static_error_page, handle_error_and_jsonify } from '../utils.js';
|
||||
import {
|
||||
handle_action_json_request,
|
||||
handle_action_request,
|
||||
is_action_json_request,
|
||||
is_action_request
|
||||
} from './actions.js';
|
||||
import { load_data, load_server_data } from './load_data.js';
|
||||
import { render_response } from './render.js';
|
||||
import { respond_with_error } from './respond_with_error.js';
|
||||
import { get_option } from '../../../utils/options.js';
|
||||
import { get_data_json } from '../data/index.js';
|
||||
|
||||
/**
|
||||
* The maximum request depth permitted before assuming we're stuck in an infinite loop
|
||||
*/
|
||||
const MAX_DEPTH = 10;
|
||||
|
||||
/**
|
||||
* @param {import('@sveltejs/kit').RequestEvent} event
|
||||
* @param {import('types').PageNodeIndexes} page
|
||||
* @param {import('types').SSROptions} options
|
||||
* @param {import('@sveltejs/kit').SSRManifest} manifest
|
||||
* @param {import('types').SSRState} state
|
||||
* @param {import('types').RequiredResolveOptions} resolve_opts
|
||||
* @returns {Promise<Response>}
|
||||
*/
|
||||
export async function render_page(event, page, options, manifest, state, resolve_opts) {
|
||||
if (state.depth > MAX_DEPTH) {
|
||||
// infinite request cycle detected
|
||||
return text(`Not found: ${event.url.pathname}`, {
|
||||
status: 404 // TODO in some cases this should be 500. not sure how to differentiate
|
||||
});
|
||||
}
|
||||
|
||||
if (is_action_json_request(event)) {
|
||||
const node = await manifest._.nodes[page.leaf]();
|
||||
return handle_action_json_request(event, options, node?.server);
|
||||
}
|
||||
|
||||
try {
|
||||
const nodes = await Promise.all([
|
||||
// we use == here rather than === because [undefined] serializes as "[null]"
|
||||
...page.layouts.map((n) => (n == undefined ? n : manifest._.nodes[n]())),
|
||||
manifest._.nodes[page.leaf]()
|
||||
]);
|
||||
|
||||
const leaf_node = /** @type {import('types').SSRNode} */ (nodes.at(-1));
|
||||
|
||||
let status = 200;
|
||||
|
||||
/** @type {import('@sveltejs/kit').ActionResult | undefined} */
|
||||
let action_result = undefined;
|
||||
|
||||
if (is_action_request(event)) {
|
||||
// for action requests, first call handler in +page.server.js
|
||||
// (this also determines status code)
|
||||
action_result = await handle_action_request(event, leaf_node.server);
|
||||
if (action_result?.type === 'redirect') {
|
||||
return redirect_response(action_result.status, action_result.location);
|
||||
}
|
||||
if (action_result?.type === 'error') {
|
||||
const error = action_result.error;
|
||||
status = error instanceof HttpError ? error.status : 500;
|
||||
}
|
||||
if (action_result?.type === 'failure') {
|
||||
status = action_result.status;
|
||||
}
|
||||
}
|
||||
|
||||
const should_prerender_data = nodes.some((node) => node?.server);
|
||||
const data_pathname = add_data_suffix(event.url.pathname);
|
||||
|
||||
// it's crucial that we do this before returning the non-SSR response, otherwise
|
||||
// SvelteKit will erroneously believe that the path has been prerendered,
|
||||
// causing functions to be omitted from the manifesst generated later
|
||||
const should_prerender = get_option(nodes, 'prerender') ?? false;
|
||||
if (should_prerender) {
|
||||
const mod = leaf_node.server;
|
||||
if (mod?.actions) {
|
||||
throw new Error('Cannot prerender pages with actions');
|
||||
}
|
||||
} else if (state.prerendering) {
|
||||
// if the page isn't marked as prerenderable, then bail out at this point
|
||||
return new Response(undefined, {
|
||||
status: 204
|
||||
});
|
||||
}
|
||||
|
||||
// if we fetch any endpoints while loading data for this page, they should
|
||||
// inherit the prerender option of the page
|
||||
state.prerender_default = should_prerender;
|
||||
|
||||
/** @type {import('./types.js').Fetched[]} */
|
||||
const fetched = [];
|
||||
|
||||
if (get_option(nodes, 'ssr') === false && !state.prerendering) {
|
||||
return await render_response({
|
||||
branch: [],
|
||||
fetched,
|
||||
page_config: {
|
||||
ssr: false,
|
||||
csr: get_option(nodes, 'csr') ?? true
|
||||
},
|
||||
status,
|
||||
error: null,
|
||||
event,
|
||||
options,
|
||||
manifest,
|
||||
state,
|
||||
resolve_opts
|
||||
});
|
||||
}
|
||||
|
||||
/** @type {Array<import('./types.js').Loaded | null>} */
|
||||
const branch = [];
|
||||
|
||||
/** @type {Error | null} */
|
||||
let load_error = null;
|
||||
|
||||
/** @type {Array<Promise<import('types').ServerDataNode | null>>} */
|
||||
const server_promises = nodes.map((node, i) => {
|
||||
if (load_error) {
|
||||
// if an error happens immediately, don't bother with the rest of the nodes
|
||||
throw load_error;
|
||||
}
|
||||
|
||||
return Promise.resolve().then(async () => {
|
||||
try {
|
||||
if (node === leaf_node && action_result?.type === 'error') {
|
||||
// we wait until here to throw the error so that we can use
|
||||
// any nested +error.svelte components that were defined
|
||||
throw action_result.error;
|
||||
}
|
||||
|
||||
return await load_server_data({
|
||||
event,
|
||||
state,
|
||||
node,
|
||||
parent: async () => {
|
||||
/** @type {Record<string, any>} */
|
||||
const data = {};
|
||||
for (let j = 0; j < i; j += 1) {
|
||||
const parent = await server_promises[j];
|
||||
if (parent) Object.assign(data, await parent.data);
|
||||
}
|
||||
return data;
|
||||
},
|
||||
track_server_fetches: options.track_server_fetches
|
||||
});
|
||||
} catch (e) {
|
||||
load_error = /** @type {Error} */ (e);
|
||||
throw load_error;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
const csr = get_option(nodes, 'csr') ?? true;
|
||||
|
||||
/** @type {Array<Promise<Record<string, any> | null>>} */
|
||||
const load_promises = nodes.map((node, i) => {
|
||||
if (load_error) throw load_error;
|
||||
return Promise.resolve().then(async () => {
|
||||
try {
|
||||
return await load_data({
|
||||
event,
|
||||
fetched,
|
||||
node,
|
||||
parent: async () => {
|
||||
const data = {};
|
||||
for (let j = 0; j < i; j += 1) {
|
||||
Object.assign(data, await load_promises[j]);
|
||||
}
|
||||
return data;
|
||||
},
|
||||
resolve_opts,
|
||||
server_data_promise: server_promises[i],
|
||||
state,
|
||||
csr
|
||||
});
|
||||
} catch (e) {
|
||||
load_error = /** @type {Error} */ (e);
|
||||
throw load_error;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// if we don't do this, rejections will be unhandled
|
||||
for (const p of server_promises) p.catch(() => {});
|
||||
for (const p of load_promises) p.catch(() => {});
|
||||
|
||||
for (let i = 0; i < nodes.length; i += 1) {
|
||||
const node = nodes[i];
|
||||
|
||||
if (node) {
|
||||
try {
|
||||
const server_data = await server_promises[i];
|
||||
const data = await load_promises[i];
|
||||
|
||||
branch.push({ node, server_data, data });
|
||||
} catch (e) {
|
||||
const err = normalize_error(e);
|
||||
|
||||
if (err instanceof Redirect) {
|
||||
if (state.prerendering && should_prerender_data) {
|
||||
const body = JSON.stringify({
|
||||
type: 'redirect',
|
||||
location: err.location
|
||||
});
|
||||
|
||||
state.prerendering.dependencies.set(data_pathname, {
|
||||
response: text(body),
|
||||
body
|
||||
});
|
||||
}
|
||||
|
||||
return redirect_response(err.status, err.location);
|
||||
}
|
||||
|
||||
const status = err instanceof HttpError ? err.status : 500;
|
||||
const error = await handle_error_and_jsonify(event, options, err);
|
||||
|
||||
while (i--) {
|
||||
if (page.errors[i]) {
|
||||
const index = /** @type {number} */ (page.errors[i]);
|
||||
const node = await manifest._.nodes[index]();
|
||||
|
||||
let j = i;
|
||||
while (!branch[j]) j -= 1;
|
||||
|
||||
return await render_response({
|
||||
event,
|
||||
options,
|
||||
manifest,
|
||||
state,
|
||||
resolve_opts,
|
||||
page_config: { ssr: true, csr: true },
|
||||
status,
|
||||
error,
|
||||
branch: compact(branch.slice(0, j + 1)).concat({
|
||||
node,
|
||||
data: null,
|
||||
server_data: null
|
||||
}),
|
||||
fetched
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// if we're still here, it means the error happened in the root layout,
|
||||
// which means we have to fall back to error.html
|
||||
return static_error_page(options, status, error.message);
|
||||
}
|
||||
} else {
|
||||
// push an empty slot so we can rewind past gaps to the
|
||||
// layout that corresponds with an +error.svelte page
|
||||
branch.push(null);
|
||||
}
|
||||
}
|
||||
|
||||
if (state.prerendering && should_prerender_data) {
|
||||
// ndjson format
|
||||
let { data, chunks } = get_data_json(
|
||||
event,
|
||||
options,
|
||||
branch.map((node) => node?.server_data)
|
||||
);
|
||||
|
||||
if (chunks) {
|
||||
for await (const chunk of chunks) {
|
||||
data += chunk;
|
||||
}
|
||||
}
|
||||
|
||||
state.prerendering.dependencies.set(data_pathname, {
|
||||
response: text(data),
|
||||
body: data
|
||||
});
|
||||
}
|
||||
|
||||
return await render_response({
|
||||
event,
|
||||
options,
|
||||
manifest,
|
||||
state,
|
||||
resolve_opts,
|
||||
page_config: {
|
||||
csr: get_option(nodes, 'csr') ?? true,
|
||||
ssr: true
|
||||
},
|
||||
status,
|
||||
error: null,
|
||||
branch: compact(branch),
|
||||
action_result,
|
||||
fetched
|
||||
});
|
||||
} catch (e) {
|
||||
// if we end up here, it means the data loaded successfully
|
||||
// but the page failed to render, or that a prerendering error occurred
|
||||
return await respond_with_error({
|
||||
event,
|
||||
options,
|
||||
manifest,
|
||||
state,
|
||||
status: 500,
|
||||
error: e,
|
||||
resolve_opts
|
||||
});
|
||||
}
|
||||
}
|
369
node_modules/@sveltejs/kit/src/runtime/server/page/load_data.js
generated
vendored
Normal file
369
node_modules/@sveltejs/kit/src/runtime/server/page/load_data.js
generated
vendored
Normal file
@ -0,0 +1,369 @@
|
||||
import { disable_search, make_trackable } from '../../../utils/url.js';
|
||||
import { unwrap_promises } from '../../../utils/promises.js';
|
||||
import { DEV } from 'esm-env';
|
||||
import { validate_depends } from '../../shared.js';
|
||||
|
||||
/**
|
||||
* Calls the user's server `load` function.
|
||||
* @param {{
|
||||
* event: import('@sveltejs/kit').RequestEvent;
|
||||
* state: import('types').SSRState;
|
||||
* node: import('types').SSRNode | undefined;
|
||||
* parent: () => Promise<Record<string, any>>;
|
||||
* track_server_fetches: boolean;
|
||||
* }} opts
|
||||
* @returns {Promise<import('types').ServerDataNode | null>}
|
||||
*/
|
||||
export async function load_server_data({
|
||||
event,
|
||||
state,
|
||||
node,
|
||||
parent,
|
||||
// TODO 2.0: Remove this
|
||||
track_server_fetches
|
||||
}) {
|
||||
if (!node?.server) return null;
|
||||
|
||||
let done = false;
|
||||
|
||||
const uses = {
|
||||
dependencies: new Set(),
|
||||
params: new Set(),
|
||||
parent: false,
|
||||
route: false,
|
||||
url: false
|
||||
};
|
||||
|
||||
const url = make_trackable(event.url, () => {
|
||||
if (DEV && done && !uses.url) {
|
||||
console.warn(
|
||||
`${node.server_id}: Accessing URL properties in a promise handler after \`load(...)\` has returned will not cause the function to re-run when the URL changes`
|
||||
);
|
||||
}
|
||||
|
||||
uses.url = true;
|
||||
});
|
||||
|
||||
if (state.prerendering) {
|
||||
disable_search(url);
|
||||
}
|
||||
|
||||
const result = await node.server.load?.call(null, {
|
||||
...event,
|
||||
fetch: (info, init) => {
|
||||
const url = new URL(info instanceof Request ? info.url : info, event.url);
|
||||
|
||||
if (DEV && done && !uses.dependencies.has(url.href)) {
|
||||
console.warn(
|
||||
`${node.server_id}: Calling \`event.fetch(...)\` in a promise handler after \`load(...)\` has returned will not cause the function to re-run when the dependency is invalidated`
|
||||
);
|
||||
}
|
||||
|
||||
// TODO 2.0: Remove this
|
||||
if (track_server_fetches) {
|
||||
uses.dependencies.add(url.href);
|
||||
}
|
||||
|
||||
return event.fetch(info, init);
|
||||
},
|
||||
/** @param {string[]} deps */
|
||||
depends: (...deps) => {
|
||||
for (const dep of deps) {
|
||||
const { href } = new URL(dep, event.url);
|
||||
|
||||
if (DEV) {
|
||||
validate_depends(node.server_id, dep);
|
||||
|
||||
if (done && !uses.dependencies.has(href)) {
|
||||
console.warn(
|
||||
`${node.server_id}: Calling \`depends(...)\` in a promise handler after \`load(...)\` has returned will not cause the function to re-run when the dependency is invalidated`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
uses.dependencies.add(href);
|
||||
}
|
||||
},
|
||||
params: new Proxy(event.params, {
|
||||
get: (target, key) => {
|
||||
if (DEV && done && typeof key === 'string' && !uses.params.has(key)) {
|
||||
console.warn(
|
||||
`${node.server_id}: Accessing \`params.${String(
|
||||
key
|
||||
)}\` in a promise handler after \`load(...)\` has returned will not cause the function to re-run when the param changes`
|
||||
);
|
||||
}
|
||||
|
||||
uses.params.add(key);
|
||||
return target[/** @type {string} */ (key)];
|
||||
}
|
||||
}),
|
||||
parent: async () => {
|
||||
if (DEV && done && !uses.parent) {
|
||||
console.warn(
|
||||
`${node.server_id}: Calling \`parent(...)\` in a promise handler after \`load(...)\` has returned will not cause the function to re-run when parent data changes`
|
||||
);
|
||||
}
|
||||
|
||||
uses.parent = true;
|
||||
return parent();
|
||||
},
|
||||
route: new Proxy(event.route, {
|
||||
get: (target, key) => {
|
||||
if (DEV && done && typeof key === 'string' && !uses.route) {
|
||||
console.warn(
|
||||
`${node.server_id}: Accessing \`route.${String(
|
||||
key
|
||||
)}\` in a promise handler after \`load(...)\` has returned will not cause the function to re-run when the route changes`
|
||||
);
|
||||
}
|
||||
|
||||
uses.route = true;
|
||||
return target[/** @type {'id'} */ (key)];
|
||||
}
|
||||
}),
|
||||
url
|
||||
});
|
||||
|
||||
const data = result ? await unwrap_promises(result) : null;
|
||||
if (__SVELTEKIT_DEV__) {
|
||||
validate_load_response(data, /** @type {string} */ (event.route.id));
|
||||
}
|
||||
|
||||
done = true;
|
||||
|
||||
return {
|
||||
type: 'data',
|
||||
data,
|
||||
uses,
|
||||
slash: node.server.trailingSlash
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls the user's `load` function.
|
||||
* @param {{
|
||||
* event: import('@sveltejs/kit').RequestEvent;
|
||||
* fetched: import('./types.js').Fetched[];
|
||||
* node: import('types').SSRNode | undefined;
|
||||
* parent: () => Promise<Record<string, any>>;
|
||||
* resolve_opts: import('types').RequiredResolveOptions;
|
||||
* server_data_promise: Promise<import('types').ServerDataNode | null>;
|
||||
* state: import('types').SSRState;
|
||||
* csr: boolean;
|
||||
* }} opts
|
||||
* @returns {Promise<Record<string, any | Promise<any>> | null>}
|
||||
*/
|
||||
export async function load_data({
|
||||
event,
|
||||
fetched,
|
||||
node,
|
||||
parent,
|
||||
server_data_promise,
|
||||
state,
|
||||
resolve_opts,
|
||||
csr
|
||||
}) {
|
||||
const server_data_node = await server_data_promise;
|
||||
|
||||
if (!node?.universal?.load) {
|
||||
return server_data_node?.data ?? null;
|
||||
}
|
||||
|
||||
const result = await node.universal.load.call(null, {
|
||||
url: event.url,
|
||||
params: event.params,
|
||||
data: server_data_node?.data ?? null,
|
||||
route: event.route,
|
||||
fetch: create_universal_fetch(event, state, fetched, csr, resolve_opts),
|
||||
setHeaders: event.setHeaders,
|
||||
depends: () => {},
|
||||
parent
|
||||
});
|
||||
|
||||
const data = result ? await unwrap_promises(result) : null;
|
||||
if (__SVELTEKIT_DEV__) {
|
||||
validate_load_response(data, /** @type {string} */ (event.route.id));
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Pick<import('@sveltejs/kit').RequestEvent, 'fetch' | 'url' | 'request' | 'route'>} event
|
||||
* @param {import('types').SSRState} state
|
||||
* @param {import('./types.js').Fetched[]} fetched
|
||||
* @param {boolean} csr
|
||||
* @param {Pick<Required<import('@sveltejs/kit').ResolveOptions>, 'filterSerializedResponseHeaders'>} resolve_opts
|
||||
*/
|
||||
export function create_universal_fetch(event, state, fetched, csr, resolve_opts) {
|
||||
/**
|
||||
* @param {URL | RequestInfo} input
|
||||
* @param {RequestInit} [init]
|
||||
*/
|
||||
return async (input, init) => {
|
||||
const cloned_body = input instanceof Request && input.body ? input.clone().body : null;
|
||||
|
||||
const cloned_headers =
|
||||
input instanceof Request && [...input.headers].length
|
||||
? new Headers(input.headers)
|
||||
: init?.headers;
|
||||
|
||||
let response = await event.fetch(input, init);
|
||||
|
||||
const url = new URL(input instanceof Request ? input.url : input, event.url);
|
||||
const same_origin = url.origin === event.url.origin;
|
||||
|
||||
/** @type {import('types').PrerenderDependency} */
|
||||
let dependency;
|
||||
|
||||
if (same_origin) {
|
||||
if (state.prerendering) {
|
||||
dependency = { response, body: null };
|
||||
state.prerendering.dependencies.set(url.pathname, dependency);
|
||||
}
|
||||
} else {
|
||||
// simulate CORS errors and "no access to body in no-cors mode" server-side for consistency with client-side behaviour
|
||||
const mode = input instanceof Request ? input.mode : init?.mode ?? 'cors';
|
||||
if (mode === 'no-cors') {
|
||||
response = new Response('', {
|
||||
status: response.status,
|
||||
statusText: response.statusText,
|
||||
headers: response.headers
|
||||
});
|
||||
} else {
|
||||
const acao = response.headers.get('access-control-allow-origin');
|
||||
if (!acao || (acao !== event.url.origin && acao !== '*')) {
|
||||
throw new Error(
|
||||
`CORS error: ${
|
||||
acao ? 'Incorrect' : 'No'
|
||||
} 'Access-Control-Allow-Origin' header is present on the requested resource`
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const proxy = new Proxy(response, {
|
||||
get(response, key, _receiver) {
|
||||
async function text() {
|
||||
const body = await response.text();
|
||||
|
||||
if (!body || typeof body === 'string') {
|
||||
const status_number = Number(response.status);
|
||||
if (isNaN(status_number)) {
|
||||
throw new Error(
|
||||
`response.status is not a number. value: "${
|
||||
response.status
|
||||
}" type: ${typeof response.status}`
|
||||
);
|
||||
}
|
||||
|
||||
fetched.push({
|
||||
url: same_origin ? url.href.slice(event.url.origin.length) : url.href,
|
||||
method: event.request.method,
|
||||
request_body: /** @type {string | ArrayBufferView | undefined} */ (
|
||||
input instanceof Request && cloned_body
|
||||
? await stream_to_string(cloned_body)
|
||||
: init?.body
|
||||
),
|
||||
request_headers: cloned_headers,
|
||||
response_body: body,
|
||||
response
|
||||
});
|
||||
}
|
||||
|
||||
if (dependency) {
|
||||
dependency.body = body;
|
||||
}
|
||||
|
||||
return body;
|
||||
}
|
||||
|
||||
if (key === 'arrayBuffer') {
|
||||
return async () => {
|
||||
const buffer = await response.arrayBuffer();
|
||||
|
||||
if (dependency) {
|
||||
dependency.body = new Uint8Array(buffer);
|
||||
}
|
||||
|
||||
// TODO should buffer be inlined into the page (albeit base64'd)?
|
||||
// any conditions in which it shouldn't be?
|
||||
|
||||
return buffer;
|
||||
};
|
||||
}
|
||||
|
||||
if (key === 'text') {
|
||||
return text;
|
||||
}
|
||||
|
||||
if (key === 'json') {
|
||||
return async () => {
|
||||
return JSON.parse(await text());
|
||||
};
|
||||
}
|
||||
|
||||
return Reflect.get(response, key, response);
|
||||
}
|
||||
});
|
||||
|
||||
if (csr) {
|
||||
// ensure that excluded headers can't be read
|
||||
const get = response.headers.get;
|
||||
response.headers.get = (key) => {
|
||||
const lower = key.toLowerCase();
|
||||
const value = get.call(response.headers, lower);
|
||||
if (value && !lower.startsWith('x-sveltekit-')) {
|
||||
const included = resolve_opts.filterSerializedResponseHeaders(lower, value);
|
||||
if (!included) {
|
||||
throw new Error(
|
||||
`Failed to get response header "${lower}" — it must be included by the \`filterSerializedResponseHeaders\` option: https://kit.svelte.dev/docs/hooks#server-hooks-handle (at ${event.route.id})`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return value;
|
||||
};
|
||||
}
|
||||
|
||||
return proxy;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {ReadableStream<Uint8Array>} stream
|
||||
*/
|
||||
async function stream_to_string(stream) {
|
||||
let result = '';
|
||||
const reader = stream.getReader();
|
||||
const decoder = new TextDecoder();
|
||||
while (true) {
|
||||
const { done, value } = await reader.read();
|
||||
if (done) {
|
||||
break;
|
||||
}
|
||||
result += decoder.decode(value);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {any} data
|
||||
* @param {string} [routeId]
|
||||
*/
|
||||
function validate_load_response(data, routeId) {
|
||||
if (data != null && Object.getPrototypeOf(data) !== Object.prototype) {
|
||||
throw new Error(
|
||||
`a load function related to route '${routeId}' returned ${
|
||||
typeof data !== 'object'
|
||||
? `a ${typeof data}`
|
||||
: data instanceof Response
|
||||
? 'a Response object'
|
||||
: Array.isArray(data)
|
||||
? 'an array'
|
||||
: 'a non-plain object'
|
||||
}, but must return a plain object at the top level (i.e. \`return {...}\`)`
|
||||
);
|
||||
}
|
||||
}
|
564
node_modules/@sveltejs/kit/src/runtime/server/page/render.js
generated
vendored
Normal file
564
node_modules/@sveltejs/kit/src/runtime/server/page/render.js
generated
vendored
Normal file
@ -0,0 +1,564 @@
|
||||
import * as devalue from 'devalue';
|
||||
import { readable, writable } from 'svelte/store';
|
||||
import { DEV } from 'esm-env';
|
||||
import * as paths from '__sveltekit/paths';
|
||||
import { hash } from '../../hash.js';
|
||||
import { serialize_data } from './serialize_data.js';
|
||||
import { s } from '../../../utils/misc.js';
|
||||
import { Csp } from './csp.js';
|
||||
import { uneval_action_response } from './actions.js';
|
||||
import { clarify_devalue_error, stringify_uses, handle_error_and_jsonify } from '../utils.js';
|
||||
import { public_env } from '../../shared-server.js';
|
||||
import { text } from '../../../exports/index.js';
|
||||
import { create_async_iterator } from '../../../utils/streaming.js';
|
||||
import { SVELTE_KIT_ASSETS } from '../../../constants.js';
|
||||
import { SCHEME } from '../../../utils/url.js';
|
||||
|
||||
// TODO rename this function/module
|
||||
|
||||
const updated = {
|
||||
...readable(false),
|
||||
check: () => false
|
||||
};
|
||||
|
||||
const encoder = new TextEncoder();
|
||||
|
||||
/**
|
||||
* Creates the HTML response.
|
||||
* @param {{
|
||||
* branch: Array<import('./types.js').Loaded>;
|
||||
* fetched: Array<import('./types.js').Fetched>;
|
||||
* options: import('types').SSROptions;
|
||||
* manifest: import('@sveltejs/kit').SSRManifest;
|
||||
* state: import('types').SSRState;
|
||||
* page_config: { ssr: boolean; csr: boolean };
|
||||
* status: number;
|
||||
* error: App.Error | null;
|
||||
* event: import('@sveltejs/kit').RequestEvent;
|
||||
* resolve_opts: import('types').RequiredResolveOptions;
|
||||
* action_result?: import('@sveltejs/kit').ActionResult;
|
||||
* }} opts
|
||||
*/
|
||||
export async function render_response({
|
||||
branch,
|
||||
fetched,
|
||||
options,
|
||||
manifest,
|
||||
state,
|
||||
page_config,
|
||||
status,
|
||||
error = null,
|
||||
event,
|
||||
resolve_opts,
|
||||
action_result
|
||||
}) {
|
||||
if (state.prerendering) {
|
||||
if (options.csp.mode === 'nonce') {
|
||||
throw new Error('Cannot use prerendering if config.kit.csp.mode === "nonce"');
|
||||
}
|
||||
|
||||
if (options.app_template_contains_nonce) {
|
||||
throw new Error('Cannot use prerendering if page template contains %sveltekit.nonce%');
|
||||
}
|
||||
}
|
||||
|
||||
const { client } = manifest._;
|
||||
|
||||
const modulepreloads = new Set(client.imports);
|
||||
const stylesheets = new Set(client.stylesheets);
|
||||
const fonts = new Set(client.fonts);
|
||||
|
||||
/** @type {Set<string>} */
|
||||
const link_header_preloads = new Set();
|
||||
|
||||
/** @type {Map<string, string>} */
|
||||
// TODO if we add a client entry point one day, we will need to include inline_styles with the entry, otherwise stylesheets will be linked even if they are below inlineStyleThreshold
|
||||
const inline_styles = new Map();
|
||||
|
||||
let rendered;
|
||||
|
||||
const form_value =
|
||||
action_result?.type === 'success' || action_result?.type === 'failure'
|
||||
? action_result.data ?? null
|
||||
: null;
|
||||
|
||||
/** @type {string} */
|
||||
let base = paths.base;
|
||||
|
||||
/** @type {string} */
|
||||
let assets = paths.assets;
|
||||
|
||||
/**
|
||||
* An expression that will evaluate in the client to determine the resolved base path.
|
||||
* We use a relative path when possible to support IPFS, the internet archive, etc.
|
||||
*/
|
||||
let base_expression = s(paths.base);
|
||||
|
||||
// if appropriate, use relative paths for greater portability
|
||||
if (paths.relative !== false && !state.prerendering?.fallback) {
|
||||
const segments = event.url.pathname.slice(paths.base.length).split('/').slice(2);
|
||||
|
||||
base = segments.map(() => '..').join('/') || '.';
|
||||
|
||||
// resolve e.g. '../..' against current location, then remove trailing slash
|
||||
base_expression = `new URL(${s(base)}, location).pathname.slice(0, -1)`;
|
||||
|
||||
if (!paths.assets || (paths.assets[0] === '/' && paths.assets !== SVELTE_KIT_ASSETS)) {
|
||||
assets = base;
|
||||
}
|
||||
}
|
||||
|
||||
if (page_config.ssr) {
|
||||
if (__SVELTEKIT_DEV__ && !branch.at(-1)?.node.component) {
|
||||
// Can only be the leaf, layouts have a fallback component generated
|
||||
throw new Error(`Missing +page.svelte component for route ${event.route.id}`);
|
||||
}
|
||||
|
||||
/** @type {Record<string, any>} */
|
||||
const props = {
|
||||
stores: {
|
||||
page: writable(null),
|
||||
navigating: writable(null),
|
||||
updated
|
||||
},
|
||||
constructors: await Promise.all(branch.map(({ node }) => node.component())),
|
||||
form: form_value
|
||||
};
|
||||
|
||||
let data = {};
|
||||
|
||||
// props_n (instead of props[n]) makes it easy to avoid
|
||||
// unnecessary updates for layout components
|
||||
for (let i = 0; i < branch.length; i += 1) {
|
||||
data = { ...data, ...branch[i].data };
|
||||
props[`data_${i}`] = data;
|
||||
}
|
||||
|
||||
props.page = {
|
||||
error,
|
||||
params: /** @type {Record<string, any>} */ (event.params),
|
||||
route: event.route,
|
||||
status,
|
||||
url: event.url,
|
||||
data,
|
||||
form: form_value
|
||||
};
|
||||
|
||||
// use relative paths during rendering, so that the resulting HTML is as
|
||||
// portable as possible, but reset afterwards
|
||||
if (paths.relative) paths.override({ base, assets });
|
||||
|
||||
if (__SVELTEKIT_DEV__) {
|
||||
const fetch = globalThis.fetch;
|
||||
let warned = false;
|
||||
globalThis.fetch = (info, init) => {
|
||||
if (typeof info === 'string' && !SCHEME.test(info)) {
|
||||
throw new Error(
|
||||
`Cannot call \`fetch\` eagerly during server side rendering with relative URL (${info}) — put your \`fetch\` calls inside \`onMount\` or a \`load\` function instead`
|
||||
);
|
||||
} else if (!warned) {
|
||||
console.warn(
|
||||
'Avoid calling `fetch` eagerly during server side rendering — put your `fetch` calls inside `onMount` or a `load` function instead'
|
||||
);
|
||||
warned = true;
|
||||
}
|
||||
|
||||
return fetch(info, init);
|
||||
};
|
||||
|
||||
try {
|
||||
rendered = options.root.render(props);
|
||||
} finally {
|
||||
globalThis.fetch = fetch;
|
||||
paths.reset();
|
||||
}
|
||||
} else {
|
||||
try {
|
||||
rendered = options.root.render(props);
|
||||
} finally {
|
||||
paths.reset();
|
||||
}
|
||||
}
|
||||
|
||||
for (const { node } of branch) {
|
||||
for (const url of node.imports) modulepreloads.add(url);
|
||||
for (const url of node.stylesheets) stylesheets.add(url);
|
||||
for (const url of node.fonts) fonts.add(url);
|
||||
|
||||
if (node.inline_styles) {
|
||||
Object.entries(await node.inline_styles()).forEach(([k, v]) => inline_styles.set(k, v));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
rendered = { head: '', html: '', css: { code: '', map: null } };
|
||||
}
|
||||
|
||||
let head = '';
|
||||
let body = rendered.html;
|
||||
|
||||
const csp = new Csp(options.csp, {
|
||||
prerender: !!state.prerendering
|
||||
});
|
||||
|
||||
/** @param {string} path */
|
||||
const prefixed = (path) => {
|
||||
if (path.startsWith('/')) {
|
||||
// Vite makes the start script available through the base path and without it.
|
||||
// We load it via the base path in order to support remote IDE environments which proxy
|
||||
// all URLs under the base path during development.
|
||||
return paths.base + path;
|
||||
}
|
||||
return `${assets}/${path}`;
|
||||
};
|
||||
|
||||
if (inline_styles.size > 0) {
|
||||
const content = Array.from(inline_styles.values()).join('\n');
|
||||
|
||||
const attributes = __SVELTEKIT_DEV__ ? [' data-sveltekit'] : [];
|
||||
if (csp.style_needs_nonce) attributes.push(` nonce="${csp.nonce}"`);
|
||||
|
||||
csp.add_style(content);
|
||||
|
||||
head += `\n\t<style${attributes.join('')}>${content}</style>`;
|
||||
}
|
||||
|
||||
for (const dep of stylesheets) {
|
||||
const path = prefixed(dep);
|
||||
|
||||
const attributes = ['rel="stylesheet"'];
|
||||
|
||||
if (inline_styles.has(dep)) {
|
||||
// don't load stylesheets that are already inlined
|
||||
// include them in disabled state so that Vite can detect them and doesn't try to add them
|
||||
attributes.push('disabled', 'media="(max-width: 0)"');
|
||||
} else {
|
||||
if (resolve_opts.preload({ type: 'css', path })) {
|
||||
const preload_atts = ['rel="preload"', 'as="style"'];
|
||||
link_header_preloads.add(`<${encodeURI(path)}>; ${preload_atts.join(';')}; nopush`);
|
||||
}
|
||||
}
|
||||
|
||||
head += `\n\t\t<link href="${path}" ${attributes.join(' ')}>`;
|
||||
}
|
||||
|
||||
for (const dep of fonts) {
|
||||
const path = prefixed(dep);
|
||||
|
||||
if (resolve_opts.preload({ type: 'font', path })) {
|
||||
const ext = dep.slice(dep.lastIndexOf('.') + 1);
|
||||
const attributes = [
|
||||
'rel="preload"',
|
||||
'as="font"',
|
||||
`type="font/${ext}"`,
|
||||
`href="${path}"`,
|
||||
'crossorigin'
|
||||
];
|
||||
|
||||
head += `\n\t\t<link ${attributes.join(' ')}>`;
|
||||
}
|
||||
}
|
||||
|
||||
const global = __SVELTEKIT_DEV__ ? '__sveltekit_dev' : `__sveltekit_${options.version_hash}`;
|
||||
|
||||
const { data, chunks } = get_data(
|
||||
event,
|
||||
options,
|
||||
branch.map((b) => b.server_data),
|
||||
global
|
||||
);
|
||||
|
||||
if (page_config.ssr && page_config.csr) {
|
||||
body += `\n\t\t\t${fetched
|
||||
.map((item) =>
|
||||
serialize_data(item, resolve_opts.filterSerializedResponseHeaders, !!state.prerendering)
|
||||
)
|
||||
.join('\n\t\t\t')}`;
|
||||
}
|
||||
|
||||
if (page_config.csr) {
|
||||
const included_modulepreloads = Array.from(modulepreloads, (dep) => prefixed(dep)).filter(
|
||||
(path) => resolve_opts.preload({ type: 'js', path })
|
||||
);
|
||||
|
||||
for (const path of included_modulepreloads) {
|
||||
// see the kit.output.preloadStrategy option for details on why we have multiple options here
|
||||
link_header_preloads.add(`<${encodeURI(path)}>; rel="modulepreload"; nopush`);
|
||||
if (options.preload_strategy !== 'modulepreload') {
|
||||
head += `\n\t\t<link rel="preload" as="script" crossorigin="anonymous" href="${path}">`;
|
||||
} else if (state.prerendering) {
|
||||
head += `\n\t\t<link rel="modulepreload" href="${path}">`;
|
||||
}
|
||||
}
|
||||
|
||||
const blocks = [];
|
||||
|
||||
const properties = [
|
||||
paths.assets && `assets: ${s(paths.assets)}`,
|
||||
`base: ${base_expression}`,
|
||||
`env: ${s(public_env)}`
|
||||
].filter(Boolean);
|
||||
|
||||
if (chunks) {
|
||||
blocks.push('const deferred = new Map();');
|
||||
|
||||
properties.push(`defer: (id) => new Promise((fulfil, reject) => {
|
||||
deferred.set(id, { fulfil, reject });
|
||||
})`);
|
||||
|
||||
properties.push(`resolve: ({ id, data, error }) => {
|
||||
const { fulfil, reject } = deferred.get(id);
|
||||
deferred.delete(id);
|
||||
|
||||
if (error) reject(error);
|
||||
else fulfil(data);
|
||||
}`);
|
||||
}
|
||||
|
||||
blocks.push(`${global} = {
|
||||
${properties.join(',\n\t\t\t\t\t\t')}
|
||||
};`);
|
||||
|
||||
const args = ['app', 'element'];
|
||||
|
||||
blocks.push('const element = document.currentScript.parentElement;');
|
||||
|
||||
if (page_config.ssr) {
|
||||
const serialized = { form: 'null', error: 'null' };
|
||||
|
||||
blocks.push(`const data = ${data};`);
|
||||
|
||||
if (form_value) {
|
||||
serialized.form = uneval_action_response(
|
||||
form_value,
|
||||
/** @type {string} */ (event.route.id)
|
||||
);
|
||||
}
|
||||
|
||||
if (error) {
|
||||
serialized.error = devalue.uneval(error);
|
||||
}
|
||||
|
||||
const hydrate = [
|
||||
`node_ids: [${branch.map(({ node }) => node.index).join(', ')}]`,
|
||||
'data',
|
||||
`form: ${serialized.form}`,
|
||||
`error: ${serialized.error}`
|
||||
];
|
||||
|
||||
if (status !== 200) {
|
||||
hydrate.push(`status: ${status}`);
|
||||
}
|
||||
|
||||
if (options.embedded) {
|
||||
hydrate.push(`params: ${devalue.uneval(event.params)}`, `route: ${s(event.route)}`);
|
||||
}
|
||||
|
||||
args.push(`{\n\t\t\t\t\t\t\t${hydrate.join(',\n\t\t\t\t\t\t\t')}\n\t\t\t\t\t\t}`);
|
||||
}
|
||||
|
||||
blocks.push(`Promise.all([
|
||||
import(${s(prefixed(client.start))}),
|
||||
import(${s(prefixed(client.app))})
|
||||
]).then(([kit, app]) => {
|
||||
kit.start(${args.join(', ')});
|
||||
});`);
|
||||
|
||||
if (options.service_worker) {
|
||||
const opts = __SVELTEKIT_DEV__ ? ", { type: 'module' }" : '';
|
||||
|
||||
// we use an anonymous function instead of an arrow function to support
|
||||
// older browsers (https://github.com/sveltejs/kit/pull/5417)
|
||||
blocks.push(`if ('serviceWorker' in navigator) {
|
||||
addEventListener('load', function () {
|
||||
navigator.serviceWorker.register('${prefixed('service-worker.js')}'${opts});
|
||||
});
|
||||
}`);
|
||||
}
|
||||
|
||||
const init_app = `
|
||||
{
|
||||
${blocks.join('\n\n\t\t\t\t\t')}
|
||||
}
|
||||
`;
|
||||
csp.add_script(init_app);
|
||||
|
||||
body += `\n\t\t\t<script${
|
||||
csp.script_needs_nonce ? ` nonce="${csp.nonce}"` : ''
|
||||
}>${init_app}</script>\n\t\t`;
|
||||
}
|
||||
|
||||
const headers = new Headers({
|
||||
'x-sveltekit-page': 'true',
|
||||
'content-type': 'text/html'
|
||||
});
|
||||
|
||||
if (state.prerendering) {
|
||||
// TODO read headers set with setHeaders and convert into http-equiv where possible
|
||||
const http_equiv = [];
|
||||
|
||||
const csp_headers = csp.csp_provider.get_meta();
|
||||
if (csp_headers) {
|
||||
http_equiv.push(csp_headers);
|
||||
}
|
||||
|
||||
if (state.prerendering.cache) {
|
||||
http_equiv.push(`<meta http-equiv="cache-control" content="${state.prerendering.cache}">`);
|
||||
}
|
||||
|
||||
if (http_equiv.length > 0) {
|
||||
head = http_equiv.join('\n') + head;
|
||||
}
|
||||
} else {
|
||||
const csp_header = csp.csp_provider.get_header();
|
||||
if (csp_header) {
|
||||
headers.set('content-security-policy', csp_header);
|
||||
}
|
||||
const report_only_header = csp.report_only_provider.get_header();
|
||||
if (report_only_header) {
|
||||
headers.set('content-security-policy-report-only', report_only_header);
|
||||
}
|
||||
|
||||
if (link_header_preloads.size) {
|
||||
headers.set('link', Array.from(link_header_preloads).join(', '));
|
||||
}
|
||||
}
|
||||
|
||||
// add the content after the script/css links so the link elements are parsed first
|
||||
head += rendered.head;
|
||||
|
||||
const html = options.templates.app({
|
||||
head,
|
||||
body,
|
||||
assets,
|
||||
nonce: /** @type {string} */ (csp.nonce),
|
||||
env: public_env
|
||||
});
|
||||
|
||||
// TODO flush chunks as early as we can
|
||||
const transformed =
|
||||
(await resolve_opts.transformPageChunk({
|
||||
html,
|
||||
done: true
|
||||
})) || '';
|
||||
|
||||
if (!chunks) {
|
||||
headers.set('etag', `"${hash(transformed)}"`);
|
||||
}
|
||||
|
||||
if (DEV) {
|
||||
if (page_config.csr) {
|
||||
if (transformed.split('<!--').length < html.split('<!--').length) {
|
||||
// the \u001B stuff is ANSI codes, so that we don't need to add a library to the runtime
|
||||
// https://svelte.dev/repl/1b3f49696f0c44c881c34587f2537aa2
|
||||
console.warn(
|
||||
"\u001B[1m\u001B[31mRemoving comments in transformPageChunk can break Svelte's hydration\u001B[39m\u001B[22m"
|
||||
);
|
||||
}
|
||||
} else {
|
||||
if (chunks) {
|
||||
console.warn(
|
||||
'\u001B[1m\u001B[31mReturning promises from server `load` functions will only work if `csr === true`\u001B[39m\u001B[22m'
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return !chunks
|
||||
? text(transformed, {
|
||||
status,
|
||||
headers
|
||||
})
|
||||
: new Response(
|
||||
new ReadableStream({
|
||||
async start(controller) {
|
||||
controller.enqueue(encoder.encode(transformed + '\n'));
|
||||
for await (const chunk of chunks) {
|
||||
controller.enqueue(encoder.encode(chunk));
|
||||
}
|
||||
controller.close();
|
||||
},
|
||||
|
||||
type: 'bytes'
|
||||
}),
|
||||
{
|
||||
headers: {
|
||||
'content-type': 'text/html'
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* If the serialized data contains promises, `chunks` will be an
|
||||
* async iterable containing their resolutions
|
||||
* @param {import('@sveltejs/kit').RequestEvent} event
|
||||
* @param {import('types').SSROptions} options
|
||||
* @param {Array<import('types').ServerDataNode | null>} nodes
|
||||
* @param {string} global
|
||||
* @returns {{ data: string, chunks: AsyncIterable<string> | null }}
|
||||
*/
|
||||
function get_data(event, options, nodes, global) {
|
||||
let promise_id = 1;
|
||||
let count = 0;
|
||||
|
||||
const { iterator, push, done } = create_async_iterator();
|
||||
|
||||
/** @param {any} thing */
|
||||
function replacer(thing) {
|
||||
if (typeof thing?.then === 'function') {
|
||||
const id = promise_id++;
|
||||
count += 1;
|
||||
|
||||
thing
|
||||
.then(/** @param {any} data */ (data) => ({ data }))
|
||||
.catch(
|
||||
/** @param {any} error */ async (error) => ({
|
||||
error: await handle_error_and_jsonify(event, options, error)
|
||||
})
|
||||
)
|
||||
.then(
|
||||
/**
|
||||
* @param {{data: any; error: any}} result
|
||||
*/
|
||||
async ({ data, error }) => {
|
||||
count -= 1;
|
||||
|
||||
let str;
|
||||
try {
|
||||
str = devalue.uneval({ id, data, error }, replacer);
|
||||
} catch (e) {
|
||||
error = await handle_error_and_jsonify(
|
||||
event,
|
||||
options,
|
||||
new Error(`Failed to serialize promise while rendering ${event.route.id}`)
|
||||
);
|
||||
data = undefined;
|
||||
str = devalue.uneval({ id, data, error }, replacer);
|
||||
}
|
||||
|
||||
push(`<script>${global}.resolve(${str})</script>\n`);
|
||||
if (count === 0) done();
|
||||
}
|
||||
);
|
||||
|
||||
return `${global}.defer(${id})`;
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
const strings = nodes.map((node) => {
|
||||
if (!node) return 'null';
|
||||
|
||||
return `{"type":"data","data":${devalue.uneval(node.data, replacer)},${stringify_uses(node)}${
|
||||
node.slash ? `,"slash":${JSON.stringify(node.slash)}` : ''
|
||||
}}`;
|
||||
});
|
||||
|
||||
return {
|
||||
data: `[${strings.join(',')}]`,
|
||||
chunks: count > 0 ? iterator : null
|
||||
};
|
||||
} catch (e) {
|
||||
throw new Error(clarify_devalue_error(event, /** @type {any} */ (e)));
|
||||
}
|
||||
}
|
111
node_modules/@sveltejs/kit/src/runtime/server/page/respond_with_error.js
generated
vendored
Normal file
111
node_modules/@sveltejs/kit/src/runtime/server/page/respond_with_error.js
generated
vendored
Normal file
@ -0,0 +1,111 @@
|
||||
import { render_response } from './render.js';
|
||||
import { load_data, load_server_data } from './load_data.js';
|
||||
import { handle_error_and_jsonify, static_error_page, redirect_response } from '../utils.js';
|
||||
import { get_option } from '../../../utils/options.js';
|
||||
import { HttpError, Redirect } from '../../control.js';
|
||||
|
||||
/**
|
||||
* @typedef {import('./types.js').Loaded} Loaded
|
||||
*/
|
||||
|
||||
/**
|
||||
* @param {{
|
||||
* event: import('@sveltejs/kit').RequestEvent;
|
||||
* options: import('types').SSROptions;
|
||||
* manifest: import('@sveltejs/kit').SSRManifest;
|
||||
* state: import('types').SSRState;
|
||||
* status: number;
|
||||
* error: unknown;
|
||||
* resolve_opts: import('types').RequiredResolveOptions;
|
||||
* }} opts
|
||||
*/
|
||||
export async function respond_with_error({
|
||||
event,
|
||||
options,
|
||||
manifest,
|
||||
state,
|
||||
status,
|
||||
error,
|
||||
resolve_opts
|
||||
}) {
|
||||
// reroute to the fallback page to prevent an infinite chain of requests.
|
||||
if (event.request.headers.get('x-sveltekit-error')) {
|
||||
return static_error_page(options, status, /** @type {Error} */ (error).message);
|
||||
}
|
||||
|
||||
/** @type {import('./types.js').Fetched[]} */
|
||||
const fetched = [];
|
||||
|
||||
try {
|
||||
const branch = [];
|
||||
const default_layout = await manifest._.nodes[0](); // 0 is always the root layout
|
||||
const ssr = get_option([default_layout], 'ssr') ?? true;
|
||||
const csr = get_option([default_layout], 'csr') ?? true;
|
||||
|
||||
if (ssr) {
|
||||
state.error = true;
|
||||
|
||||
const server_data_promise = load_server_data({
|
||||
event,
|
||||
state,
|
||||
node: default_layout,
|
||||
parent: async () => ({}),
|
||||
track_server_fetches: options.track_server_fetches
|
||||
});
|
||||
|
||||
const server_data = await server_data_promise;
|
||||
|
||||
const data = await load_data({
|
||||
event,
|
||||
fetched,
|
||||
node: default_layout,
|
||||
parent: async () => ({}),
|
||||
resolve_opts,
|
||||
server_data_promise,
|
||||
state,
|
||||
csr
|
||||
});
|
||||
|
||||
branch.push(
|
||||
{
|
||||
node: default_layout,
|
||||
server_data,
|
||||
data
|
||||
},
|
||||
{
|
||||
node: await manifest._.nodes[1](), // 1 is always the root error
|
||||
data: null,
|
||||
server_data: null
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
return await render_response({
|
||||
options,
|
||||
manifest,
|
||||
state,
|
||||
page_config: {
|
||||
ssr,
|
||||
csr: get_option([default_layout], 'csr') ?? true
|
||||
},
|
||||
status,
|
||||
error: await handle_error_and_jsonify(event, options, error),
|
||||
branch,
|
||||
fetched,
|
||||
event,
|
||||
resolve_opts
|
||||
});
|
||||
} catch (e) {
|
||||
// Edge case: If route is a 404 and the user redirects to somewhere from the root layout,
|
||||
// we end up here.
|
||||
if (e instanceof Redirect) {
|
||||
return redirect_response(e.status, e.location);
|
||||
}
|
||||
|
||||
return static_error_page(
|
||||
options,
|
||||
e instanceof HttpError ? e.status : 500,
|
||||
(await handle_error_and_jsonify(event, options, e)).message
|
||||
);
|
||||
}
|
||||
}
|
103
node_modules/@sveltejs/kit/src/runtime/server/page/serialize_data.js
generated
vendored
Normal file
103
node_modules/@sveltejs/kit/src/runtime/server/page/serialize_data.js
generated
vendored
Normal file
@ -0,0 +1,103 @@
|
||||
import { escape_html_attr } from '../../../utils/escape.js';
|
||||
import { hash } from '../../hash.js';
|
||||
|
||||
/**
|
||||
* Inside a script element, only `</script` and `<!--` hold special meaning to the HTML parser.
|
||||
*
|
||||
* The first closes the script element, so everything after is treated as raw HTML.
|
||||
* The second disables further parsing until `-->`, so the script element might be unexpectedly
|
||||
* kept open until until an unrelated HTML comment in the page.
|
||||
*
|
||||
* U+2028 LINE SEPARATOR and U+2029 PARAGRAPH SEPARATOR are escaped for the sake of pre-2018
|
||||
* browsers.
|
||||
*
|
||||
* @see tests for unsafe parsing examples.
|
||||
* @see https://html.spec.whatwg.org/multipage/scripting.html#restrictions-for-contents-of-script-elements
|
||||
* @see https://html.spec.whatwg.org/multipage/syntax.html#cdata-rcdata-restrictions
|
||||
* @see https://html.spec.whatwg.org/multipage/parsing.html#script-data-state
|
||||
* @see https://html.spec.whatwg.org/multipage/parsing.html#script-data-double-escaped-state
|
||||
* @see https://github.com/tc39/proposal-json-superset
|
||||
* @type {Record<string, string>}
|
||||
*/
|
||||
const replacements = {
|
||||
'<': '\\u003C',
|
||||
'\u2028': '\\u2028',
|
||||
'\u2029': '\\u2029'
|
||||
};
|
||||
|
||||
const pattern = new RegExp(`[${Object.keys(replacements).join('')}]`, 'g');
|
||||
|
||||
/**
|
||||
* Generates a raw HTML string containing a safe script element carrying data and associated attributes.
|
||||
*
|
||||
* It escapes all the special characters needed to guarantee the element is unbroken, but care must
|
||||
* be taken to ensure it is inserted in the document at an acceptable position for a script element,
|
||||
* and that the resulting string isn't further modified.
|
||||
*
|
||||
* @param {import('./types.js').Fetched} fetched
|
||||
* @param {(name: string, value: string) => boolean} filter
|
||||
* @param {boolean} [prerendering]
|
||||
* @returns {string} The raw HTML of a script element carrying the JSON payload.
|
||||
* @example const html = serialize_data('/data.json', null, { foo: 'bar' });
|
||||
*/
|
||||
export function serialize_data(fetched, filter, prerendering = false) {
|
||||
/** @type {Record<string, string>} */
|
||||
const headers = {};
|
||||
|
||||
let cache_control = null;
|
||||
let age = null;
|
||||
let varyAny = false;
|
||||
|
||||
for (const [key, value] of fetched.response.headers) {
|
||||
if (filter(key, value)) {
|
||||
headers[key] = value;
|
||||
}
|
||||
|
||||
if (key === 'cache-control') cache_control = value;
|
||||
else if (key === 'age') age = value;
|
||||
else if (key === 'vary' && value.trim() === '*') varyAny = true;
|
||||
}
|
||||
|
||||
const payload = {
|
||||
status: fetched.response.status,
|
||||
statusText: fetched.response.statusText,
|
||||
headers,
|
||||
body: fetched.response_body
|
||||
};
|
||||
|
||||
const safe_payload = JSON.stringify(payload).replace(pattern, (match) => replacements[match]);
|
||||
|
||||
const attrs = [
|
||||
'type="application/json"',
|
||||
'data-sveltekit-fetched',
|
||||
`data-url=${escape_html_attr(fetched.url)}`
|
||||
];
|
||||
|
||||
if (fetched.request_headers || fetched.request_body) {
|
||||
/** @type {import('types').StrictBody[]} */
|
||||
const values = [];
|
||||
|
||||
if (fetched.request_headers) {
|
||||
values.push([...new Headers(fetched.request_headers)].join(','));
|
||||
}
|
||||
|
||||
if (fetched.request_body) {
|
||||
values.push(fetched.request_body);
|
||||
}
|
||||
|
||||
attrs.push(`data-hash="${hash(...values)}"`);
|
||||
}
|
||||
|
||||
// Compute the time the response should be cached, taking into account max-age and age.
|
||||
// Do not cache at all if a `Vary: *` header is present, as this indicates that the
|
||||
// cache is likely to get busted.
|
||||
if (!prerendering && fetched.method === 'GET' && cache_control && !varyAny) {
|
||||
const match = /s-maxage=(\d+)/g.exec(cache_control) ?? /max-age=(\d+)/g.exec(cache_control);
|
||||
if (match) {
|
||||
const ttl = +match[1] - +(age ?? '0');
|
||||
attrs.push(`data-ttl="${ttl}"`);
|
||||
}
|
||||
}
|
||||
|
||||
return `<script ${attrs.join(' ')}>${safe_payload}</script>`;
|
||||
}
|
35
node_modules/@sveltejs/kit/src/runtime/server/page/types.d.ts
generated
vendored
Normal file
35
node_modules/@sveltejs/kit/src/runtime/server/page/types.d.ts
generated
vendored
Normal file
@ -0,0 +1,35 @@
|
||||
import { CookieSerializeOptions } from 'cookie';
|
||||
import { SSRNode, CspDirectives, ServerDataNode } from 'types';
|
||||
|
||||
export interface Fetched {
|
||||
url: string;
|
||||
method: string;
|
||||
request_body?: string | ArrayBufferView | null;
|
||||
request_headers?: HeadersInit | undefined;
|
||||
response_body: string;
|
||||
response: Response;
|
||||
}
|
||||
|
||||
export type Loaded = {
|
||||
node: SSRNode;
|
||||
data: Record<string, any> | null;
|
||||
server_data: ServerDataNode | null;
|
||||
};
|
||||
|
||||
type CspMode = 'hash' | 'nonce' | 'auto';
|
||||
|
||||
export interface CspConfig {
|
||||
mode: CspMode;
|
||||
directives: CspDirectives;
|
||||
reportOnly: CspDirectives;
|
||||
}
|
||||
|
||||
export interface CspOpts {
|
||||
prerender: boolean;
|
||||
}
|
||||
|
||||
export interface Cookie {
|
||||
name: string;
|
||||
value: string;
|
||||
options: CookieSerializeOptions;
|
||||
}
|
511
node_modules/@sveltejs/kit/src/runtime/server/respond.js
generated
vendored
Normal file
511
node_modules/@sveltejs/kit/src/runtime/server/respond.js
generated
vendored
Normal file
@ -0,0 +1,511 @@
|
||||
import { DEV } from 'esm-env';
|
||||
import { base } 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,
|
||||
has_data_suffix,
|
||||
normalize_path,
|
||||
strip_data_suffix
|
||||
} 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 { Redirect } 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 { error, 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';
|
||||
|
||||
/* global __SVELTEKIT_ADAPTER_NAME__ */
|
||||
|
||||
/** @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 = error(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 });
|
||||
}
|
||||
}
|
||||
|
||||
let decoded;
|
||||
try {
|
||||
decoded = decode_pathname(url.pathname);
|
||||
} 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 (!decoded.startsWith(base)) {
|
||||
return text('Not found', { status: 404 });
|
||||
}
|
||||
decoded = decoded.slice(base.length) || '/';
|
||||
}
|
||||
|
||||
const is_data_request = has_data_suffix(decoded);
|
||||
/** @type {boolean[] | undefined} */
|
||||
let invalidated_data_nodes;
|
||||
if (is_data_request) {
|
||||
decoded = strip_data_suffix(decoded) || '/';
|
||||
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);
|
||||
}
|
||||
|
||||
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(decoded);
|
||||
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) => {
|
||||
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 Promise.all([
|
||||
// we use == here rather than === because [undefined] serializes as "[null]"
|
||||
...route.page.layouts.map((n) => (n == undefined ? n : manifest._.nodes[n]())),
|
||||
manifest._.nodes[route.page.leaf]()
|
||||
]);
|
||||
|
||||
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)
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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) {
|
||||
if ('ssr' in opts) {
|
||||
throw new Error(
|
||||
'ssr has been removed, set it in the appropriate +layout.js instead. See the PR for more information: https://github.com/sveltejs/kit/pull/6197'
|
||||
);
|
||||
}
|
||||
|
||||
resolve_opts = {
|
||||
transformPageChunk: opts.transformPageChunk || default_transform,
|
||||
filterSerializedResponseHeaders: opts.filterSerializedResponseHeaders || default_filter,
|
||||
preload: opts.preload || default_preload
|
||||
};
|
||||
}
|
||||
|
||||
if (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) {
|
||||
return await fetch(request, {
|
||||
headers: {
|
||||
'x-sveltekit-error': 'true'
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
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 Error(`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');
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
161
node_modules/@sveltejs/kit/src/runtime/server/utils.js
generated
vendored
Normal file
161
node_modules/@sveltejs/kit/src/runtime/server/utils.js
generated
vendored
Normal file
@ -0,0 +1,161 @@
|
||||
import { DEV } from 'esm-env';
|
||||
import { json, text } from '../../exports/index.js';
|
||||
import { coalesce_to_error } from '../../utils/error.js';
|
||||
import { negotiate } from '../../utils/http.js';
|
||||
import { HttpError } from '../control.js';
|
||||
import { fix_stack_trace } from '../shared-server.js';
|
||||
import { ENDPOINT_METHODS } from '../../constants.js';
|
||||
|
||||
/** @param {any} body */
|
||||
export function is_pojo(body) {
|
||||
if (typeof body !== 'object') return false;
|
||||
|
||||
if (body) {
|
||||
if (body instanceof Uint8Array) return false;
|
||||
if (body instanceof ReadableStream) return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Partial<Record<import('types').HttpMethod, any>>} mod
|
||||
* @param {import('types').HttpMethod} method
|
||||
*/
|
||||
export function method_not_allowed(mod, method) {
|
||||
return text(`${method} method not allowed`, {
|
||||
status: 405,
|
||||
headers: {
|
||||
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/405
|
||||
// "The server must generate an Allow header field in a 405 status code response"
|
||||
allow: allowed_methods(mod).join(', ')
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/** @param {Partial<Record<import('types').HttpMethod, any>>} mod */
|
||||
export function allowed_methods(mod) {
|
||||
const allowed = Array.from(ENDPOINT_METHODS).filter((method) => method in mod);
|
||||
|
||||
if ('GET' in mod || 'HEAD' in mod) allowed.push('HEAD');
|
||||
|
||||
return allowed;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return as a response that renders the error.html
|
||||
*
|
||||
* @param {import('types').SSROptions} options
|
||||
* @param {number} status
|
||||
* @param {string} message
|
||||
*/
|
||||
export function static_error_page(options, status, message) {
|
||||
let page = options.templates.error({ status, message });
|
||||
|
||||
if (DEV) {
|
||||
// inject Vite HMR client, for easier debugging
|
||||
page = page.replace('</head>', '<script type="module" src="/@vite/client"></script></head>');
|
||||
}
|
||||
|
||||
return text(page, {
|
||||
headers: { 'content-type': 'text/html; charset=utf-8' },
|
||||
status
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {import('@sveltejs/kit').RequestEvent} event
|
||||
* @param {import('types').SSROptions} options
|
||||
* @param {unknown} error
|
||||
*/
|
||||
export async function handle_fatal_error(event, options, error) {
|
||||
error = error instanceof HttpError ? error : coalesce_to_error(error);
|
||||
const status = error instanceof HttpError ? error.status : 500;
|
||||
const body = await handle_error_and_jsonify(event, options, error);
|
||||
|
||||
// ideally we'd use sec-fetch-dest instead, but Safari — quelle surprise — doesn't support it
|
||||
const type = negotiate(event.request.headers.get('accept') || 'text/html', [
|
||||
'application/json',
|
||||
'text/html'
|
||||
]);
|
||||
|
||||
if (event.isDataRequest || type === 'application/json') {
|
||||
return json(body, {
|
||||
status
|
||||
});
|
||||
}
|
||||
|
||||
return static_error_page(options, status, body.message);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {import('@sveltejs/kit').RequestEvent} event
|
||||
* @param {import('types').SSROptions} options
|
||||
* @param {any} error
|
||||
* @returns {Promise<App.Error>}
|
||||
*/
|
||||
export async function handle_error_and_jsonify(event, options, error) {
|
||||
if (error instanceof HttpError) {
|
||||
return error.body;
|
||||
} else {
|
||||
if (__SVELTEKIT_DEV__ && typeof error == 'object') {
|
||||
fix_stack_trace(error);
|
||||
}
|
||||
|
||||
return (
|
||||
(await options.hooks.handleError({ error, event })) ?? {
|
||||
message: event.route.id != null ? 'Internal Error' : 'Not Found'
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {number} status
|
||||
* @param {string} location
|
||||
*/
|
||||
export function redirect_response(status, location) {
|
||||
const response = new Response(undefined, {
|
||||
status,
|
||||
headers: { location }
|
||||
});
|
||||
return response;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {import('@sveltejs/kit').RequestEvent} event
|
||||
* @param {Error & { path: string }} error
|
||||
*/
|
||||
export function clarify_devalue_error(event, error) {
|
||||
if (error.path) {
|
||||
return `Data returned from \`load\` while rendering ${event.route.id} is not serializable: ${error.message} (data${error.path})`;
|
||||
}
|
||||
|
||||
if (error.path === '') {
|
||||
return `Data returned from \`load\` while rendering ${event.route.id} is not a plain object`;
|
||||
}
|
||||
|
||||
// belt and braces — this should never happen
|
||||
return error.message;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {import('types').ServerDataNode} node
|
||||
*/
|
||||
export function stringify_uses(node) {
|
||||
const uses = [];
|
||||
|
||||
if (node.uses && node.uses.dependencies.size > 0) {
|
||||
uses.push(`"dependencies":${JSON.stringify(Array.from(node.uses.dependencies))}`);
|
||||
}
|
||||
|
||||
if (node.uses && node.uses.params.size > 0) {
|
||||
uses.push(`"params":${JSON.stringify(Array.from(node.uses.params))}`);
|
||||
}
|
||||
|
||||
if (node.uses?.parent) uses.push('"parent":1');
|
||||
if (node.uses?.route) uses.push('"route":1');
|
||||
if (node.uses?.url) uses.push('"url":1');
|
||||
|
||||
return `"uses":{${uses.join(',')}}`;
|
||||
}
|
23
node_modules/@sveltejs/kit/src/runtime/shared-server.js
generated
vendored
Normal file
23
node_modules/@sveltejs/kit/src/runtime/shared-server.js
generated
vendored
Normal file
@ -0,0 +1,23 @@
|
||||
/** @type {Record<string, string>} */
|
||||
export let private_env = {};
|
||||
|
||||
/** @type {Record<string, string>} */
|
||||
export let public_env = {};
|
||||
|
||||
/** @param {any} error */
|
||||
export let fix_stack_trace = (error) => error?.stack;
|
||||
|
||||
/** @type {(environment: Record<string, string>) => void} */
|
||||
export function set_private_env(environment) {
|
||||
private_env = environment;
|
||||
}
|
||||
|
||||
/** @type {(environment: Record<string, string>) => void} */
|
||||
export function set_public_env(environment) {
|
||||
public_env = environment;
|
||||
}
|
||||
|
||||
/** @param {(error: Error) => string} value */
|
||||
export function set_fix_stack_trace(value) {
|
||||
fix_stack_trace = value;
|
||||
}
|
16
node_modules/@sveltejs/kit/src/runtime/shared.js
generated
vendored
Normal file
16
node_modules/@sveltejs/kit/src/runtime/shared.js
generated
vendored
Normal file
@ -0,0 +1,16 @@
|
||||
/**
|
||||
* @param {string} route_id
|
||||
* @param {string} dep
|
||||
*/
|
||||
export function validate_depends(route_id, dep) {
|
||||
const match = /^(moz-icon|view-source|jar):/.exec(dep);
|
||||
if (match) {
|
||||
console.warn(
|
||||
`${route_id}: Calling \`depends('${dep}')\` will throw an error in Firefox because \`${match[1]}\` is a special URI scheme`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export const INVALIDATED_PARAM = 'x-sveltekit-invalidated';
|
||||
|
||||
export const TRAILING_SLASH_PARAM = 'x-sveltekit-trailing-slash';
|
Reference in New Issue
Block a user