@sveltejs/kit 1.0.0-next.537 → 1.0.0-next.538
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
import { onMount, tick } from 'svelte';
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
make_trackable,
|
|
4
|
+
decode_pathname,
|
|
5
|
+
decode_params,
|
|
6
|
+
normalize_path,
|
|
7
|
+
add_data_suffix
|
|
8
|
+
} from '../../utils/url.js';
|
|
3
9
|
import { find_anchor, get_base_uri, scroll_state } from './utils.js';
|
|
4
10
|
import {
|
|
5
11
|
lock_fetch,
|
|
@@ -992,7 +998,7 @@ export function create_client({ target, base, trailing_slash }) {
|
|
|
992
998
|
function get_navigation_intent(url, invalidating) {
|
|
993
999
|
if (is_external_url(url)) return;
|
|
994
1000
|
|
|
995
|
-
const path =
|
|
1001
|
+
const path = decode_pathname(url.pathname.slice(base.length) || '/');
|
|
996
1002
|
|
|
997
1003
|
for (const route of routes) {
|
|
998
1004
|
const params = route.exec(path);
|
|
@@ -15,9 +15,35 @@ const cookie_paths = {};
|
|
|
15
15
|
*/
|
|
16
16
|
export function get_cookies(request, url, options) {
|
|
17
17
|
const header = request.headers.get('cookie') ?? '';
|
|
18
|
-
|
|
19
18
|
const initial_cookies = parse(header);
|
|
20
19
|
|
|
20
|
+
const normalized_url = normalize_path(
|
|
21
|
+
// Remove suffix: 'foo/__data.json' would mean the cookie path is '/foo',
|
|
22
|
+
// whereas a direct hit of /foo would mean the cookie path is '/'
|
|
23
|
+
has_data_suffix(url.pathname) ? strip_data_suffix(url.pathname) : url.pathname,
|
|
24
|
+
options.trailing_slash
|
|
25
|
+
);
|
|
26
|
+
// Emulate browser-behavior: if the cookie is set at '/foo/bar', its path is '/foo'
|
|
27
|
+
const default_path = normalized_url.split('/').slice(0, -1).join('/') || '/';
|
|
28
|
+
|
|
29
|
+
if (options.dev) {
|
|
30
|
+
// Remove all cookies that no longer exist according to the request
|
|
31
|
+
for (const name of Object.keys(cookie_paths)) {
|
|
32
|
+
cookie_paths[name] = new Set(
|
|
33
|
+
[...cookie_paths[name]].filter(
|
|
34
|
+
(path) => !path_matches(normalized_url, path) || name in initial_cookies
|
|
35
|
+
)
|
|
36
|
+
);
|
|
37
|
+
}
|
|
38
|
+
// Add all new cookies we might not have seen before
|
|
39
|
+
for (const name in initial_cookies) {
|
|
40
|
+
cookie_paths[name] = cookie_paths[name] ?? new Set();
|
|
41
|
+
if (![...cookie_paths[name]].some((path) => path_matches(normalized_url, path))) {
|
|
42
|
+
cookie_paths[name].add(default_path);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
21
47
|
/** @type {Record<string, import('./page/types').Cookie>} */
|
|
22
48
|
const new_cookies = {};
|
|
23
49
|
|
|
@@ -57,9 +83,14 @@ export function get_cookies(request, url, options) {
|
|
|
57
83
|
return cookie;
|
|
58
84
|
}
|
|
59
85
|
|
|
60
|
-
|
|
86
|
+
const paths = new Set([...(cookie_paths[name] ?? [])]);
|
|
87
|
+
if (c) {
|
|
88
|
+
paths.add(c.options.path ?? default_path);
|
|
89
|
+
}
|
|
90
|
+
if (paths.size > 0) {
|
|
61
91
|
console.warn(
|
|
62
|
-
|
|
92
|
+
// prettier-ignore
|
|
93
|
+
`Cookie with name '${name}' was not found at path '${url.pathname}', but a cookie with that name exists at these paths: '${[...paths].join("', '")}'. Did you mean to set its 'path' to '/' instead?`
|
|
63
94
|
);
|
|
64
95
|
}
|
|
65
96
|
},
|
|
@@ -70,17 +101,7 @@ export function get_cookies(request, url, options) {
|
|
|
70
101
|
* @param {import('cookie').CookieSerializeOptions} opts
|
|
71
102
|
*/
|
|
72
103
|
set(name, value, opts = {}) {
|
|
73
|
-
let path = opts.path;
|
|
74
|
-
if (!path) {
|
|
75
|
-
const normalized = normalize_path(
|
|
76
|
-
// Remove suffix: 'foo/__data.json' would mean the cookie path is '/foo',
|
|
77
|
-
// whereas a direct hit of /foo would mean the cookie path is '/'
|
|
78
|
-
has_data_suffix(url.pathname) ? strip_data_suffix(url.pathname) : url.pathname,
|
|
79
|
-
options.trailing_slash
|
|
80
|
-
);
|
|
81
|
-
// Emulate browser-behavior: if the cookie is set at '/foo/bar', its path is '/foo'
|
|
82
|
-
path = normalized.split('/').slice(0, -1).join('/') || '/';
|
|
83
|
-
}
|
|
104
|
+
let path = opts.path ?? default_path;
|
|
84
105
|
|
|
85
106
|
new_cookies[name] = {
|
|
86
107
|
name,
|
|
@@ -93,11 +114,12 @@ export function get_cookies(request, url, options) {
|
|
|
93
114
|
};
|
|
94
115
|
|
|
95
116
|
if (options.dev) {
|
|
96
|
-
cookie_paths[name] = cookie_paths[name]
|
|
117
|
+
cookie_paths[name] = cookie_paths[name] ?? new Set();
|
|
97
118
|
if (!value) {
|
|
98
119
|
if (!cookie_paths[name].has(path) && cookie_paths[name].size > 0) {
|
|
120
|
+
const paths = `'${Array.from(cookie_paths[name]).join("', '")}'`;
|
|
99
121
|
console.warn(
|
|
100
|
-
`Trying to delete cookie '${name}' at path '${path}', but a cookie with that name only exists at
|
|
122
|
+
`Trying to delete cookie '${name}' at path '${path}', but a cookie with that name only exists at these paths: ${paths}.`
|
|
101
123
|
);
|
|
102
124
|
}
|
|
103
125
|
cookie_paths[name].delete(path);
|
|
@@ -5,6 +5,8 @@ import { load_server_data } from '../page/load_data.js';
|
|
|
5
5
|
import { clarify_devalue_error, handle_error_and_jsonify, serialize_data_node } from '../utils.js';
|
|
6
6
|
import { normalize_path, strip_data_suffix } from '../../../utils/url.js';
|
|
7
7
|
|
|
8
|
+
export const INVALIDATED_HEADER = 'x-sveltekit-invalidated';
|
|
9
|
+
|
|
8
10
|
/**
|
|
9
11
|
* @param {import('types').RequestEvent} event
|
|
10
12
|
* @param {import('types').SSRRoute} route
|
|
@@ -24,7 +26,7 @@ export async function render_data(event, route, options, state) {
|
|
|
24
26
|
const node_ids = [...route.page.layouts, route.page.leaf];
|
|
25
27
|
|
|
26
28
|
const invalidated =
|
|
27
|
-
event.request.headers.get(
|
|
29
|
+
event.request.headers.get(INVALIDATED_HEADER)?.split(',').map(Boolean) ??
|
|
28
30
|
node_ids.map(() => true);
|
|
29
31
|
|
|
30
32
|
let aborted = false;
|
|
@@ -6,6 +6,7 @@ import { coalesce_to_error } from '../../utils/error.js';
|
|
|
6
6
|
import { is_form_content_type } from '../../utils/http.js';
|
|
7
7
|
import { GENERIC_ERROR, handle_fatal_error } from './utils.js';
|
|
8
8
|
import {
|
|
9
|
+
decode_pathname,
|
|
9
10
|
decode_params,
|
|
10
11
|
disable_search,
|
|
11
12
|
has_data_suffix,
|
|
@@ -13,7 +14,7 @@ import {
|
|
|
13
14
|
strip_data_suffix
|
|
14
15
|
} from '../../utils/url.js';
|
|
15
16
|
import { exec } from '../../utils/routing.js';
|
|
16
|
-
import { render_data } from './data/index.js';
|
|
17
|
+
import { INVALIDATED_HEADER, render_data } from './data/index.js';
|
|
17
18
|
import { add_cookies_to_headers, get_cookies } from './cookie.js';
|
|
18
19
|
import { HttpError } from '../control.js';
|
|
19
20
|
import { create_fetch } from './fetch.js';
|
|
@@ -44,7 +45,7 @@ export async function respond(request, options, state) {
|
|
|
44
45
|
|
|
45
46
|
let decoded;
|
|
46
47
|
try {
|
|
47
|
-
decoded =
|
|
48
|
+
decoded = decode_pathname(url.pathname);
|
|
48
49
|
} catch {
|
|
49
50
|
return new Response('Malformed URI', { status: 400 });
|
|
50
51
|
}
|
|
@@ -296,14 +297,21 @@ export async function respond(request, options, state) {
|
|
|
296
297
|
resolve(event, opts).then((response) => {
|
|
297
298
|
// add headers/cookies here, rather than inside `resolve`, so that we
|
|
298
299
|
// can do it once for all responses instead of once per `return`
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
300
|
+
for (const key in headers) {
|
|
301
|
+
const value = headers[key];
|
|
302
|
+
response.headers.set(key, /** @type {string} */ (value));
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
if (is_data_request) {
|
|
306
|
+
// set the Vary header on __data.json requests to ensure we don't cache
|
|
307
|
+
// incomplete responses with skipped data loads
|
|
308
|
+
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Vary
|
|
309
|
+
const vary = response.headers.get('Vary');
|
|
310
|
+
if (vary !== '*') {
|
|
311
|
+
response.headers.append('Vary', INVALIDATED_HEADER);
|
|
305
312
|
}
|
|
306
313
|
}
|
|
314
|
+
|
|
307
315
|
add_cookies_to_headers(response.headers, Object.values(new_cookies));
|
|
308
316
|
|
|
309
317
|
if (state.prerendering && event.route.id !== null) {
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { disable_search, make_trackable } from '../../../utils/url.js';
|
|
2
2
|
import { unwrap_promises } from '../../../utils/promises.js';
|
|
3
|
+
|
|
3
4
|
/**
|
|
4
|
-
* Calls the user's `load` function.
|
|
5
|
+
* Calls the user's server `load` function.
|
|
5
6
|
* @param {{
|
|
6
7
|
* event: import('types').RequestEvent;
|
|
7
8
|
* state: import('types').SSRState;
|
|
@@ -103,6 +104,7 @@ export async function load_data({
|
|
|
103
104
|
data: server_data_node?.data ?? null,
|
|
104
105
|
route: event.route,
|
|
105
106
|
fetch: async (input, init) => {
|
|
107
|
+
const cloned_body = input instanceof Request && input.body ? input.clone().body : null;
|
|
106
108
|
const response = await event.fetch(input, init);
|
|
107
109
|
|
|
108
110
|
const url = new URL(input instanceof Request ? input.url : input, event.url);
|
|
@@ -149,7 +151,11 @@ export async function load_data({
|
|
|
149
151
|
fetched.push({
|
|
150
152
|
url: same_origin ? url.href.slice(event.url.origin.length) : url.href,
|
|
151
153
|
method: event.request.method,
|
|
152
|
-
request_body: /** @type {string | ArrayBufferView | undefined} */ (
|
|
154
|
+
request_body: /** @type {string | ArrayBufferView | undefined} */ (
|
|
155
|
+
input instanceof Request && cloned_body
|
|
156
|
+
? await stream_to_string(cloned_body)
|
|
157
|
+
: init?.body
|
|
158
|
+
),
|
|
153
159
|
response_body: body,
|
|
154
160
|
response: response
|
|
155
161
|
});
|
|
@@ -233,3 +239,20 @@ export async function load_data({
|
|
|
233
239
|
|
|
234
240
|
return data ? unwrap_promises(data) : null;
|
|
235
241
|
}
|
|
242
|
+
|
|
243
|
+
/**
|
|
244
|
+
* @param {ReadableStream<Uint8Array>} stream
|
|
245
|
+
*/
|
|
246
|
+
async function stream_to_string(stream) {
|
|
247
|
+
let result = '';
|
|
248
|
+
const reader = stream.getReader();
|
|
249
|
+
const decoder = new TextDecoder();
|
|
250
|
+
while (true) {
|
|
251
|
+
const { done, value } = await reader.read();
|
|
252
|
+
if (done) {
|
|
253
|
+
break;
|
|
254
|
+
}
|
|
255
|
+
result += decoder.decode(value);
|
|
256
|
+
}
|
|
257
|
+
return result;
|
|
258
|
+
}
|
package/src/utils/url.js
CHANGED
|
@@ -54,23 +54,20 @@ export function normalize_path(path, trailing_slash) {
|
|
|
54
54
|
return path;
|
|
55
55
|
}
|
|
56
56
|
|
|
57
|
+
/**
|
|
58
|
+
* Decode pathname excluding %25 to prevent further double decoding of params
|
|
59
|
+
* @param {string} pathname
|
|
60
|
+
*/
|
|
61
|
+
export function decode_pathname(pathname) {
|
|
62
|
+
return pathname.split('%25').map(decodeURI).join('%25');
|
|
63
|
+
}
|
|
64
|
+
|
|
57
65
|
/** @param {Record<string, string>} params */
|
|
58
66
|
export function decode_params(params) {
|
|
59
67
|
for (const key in params) {
|
|
60
68
|
// input has already been decoded by decodeURI
|
|
61
|
-
// now handle the rest
|
|
62
|
-
params[key] = params[key]
|
|
63
|
-
.replace(/%23/g, '#')
|
|
64
|
-
.replace(/%3[Bb]/g, ';')
|
|
65
|
-
.replace(/%2[Cc]/g, ',')
|
|
66
|
-
.replace(/%2[Ff]/g, '/')
|
|
67
|
-
.replace(/%3[Ff]/g, '?')
|
|
68
|
-
.replace(/%3[Aa]/g, ':')
|
|
69
|
-
.replace(/%40/g, '@')
|
|
70
|
-
.replace(/%26/g, '&')
|
|
71
|
-
.replace(/%3[Dd]/g, '=')
|
|
72
|
-
.replace(/%2[Bb]/g, '+')
|
|
73
|
-
.replace(/%24/g, '$');
|
|
69
|
+
// now handle the rest
|
|
70
|
+
params[key] = decodeURIComponent(params[key]);
|
|
74
71
|
}
|
|
75
72
|
|
|
76
73
|
return params;
|