Compare commits

...

7 Commits

Author SHA1 Message Date
Rich Harris
d9cb572271 -> v0.7.6 2018-02-28 17:14:38 -05:00
Rich Harris
34c28f36cd Merge pull request #147 from sveltejs/gh-138
don't serve error page for unhandled server route errors
2018-02-28 17:11:52 -05:00
Rich Harris
5dd04eb35c tidy up - next is unused 2018-02-28 16:59:39 -05:00
Rich Harris
b1d072d43a dont serve error page for unhandled server route errors - fixes #138 2018-02-28 16:54:38 -05:00
Rich Harris
5ad3f3f1d5 Merge pull request #146 from sveltejs/gh-145
prevent client-side navigation to server routes
2018-02-28 16:20:42 -05:00
Rich Harris
58754c6d15 use blog.json instead of blog/list.json 2018-02-28 16:17:07 -05:00
Rich Harris
c36780fdc8 prevent client-side navigation to server routes - fixes #145 2018-02-28 14:23:19 -05:00
13 changed files with 70 additions and 21 deletions

View File

@@ -1,5 +1,10 @@
# sapper changelog
## 0.7.6
* Prevent client-side navigation to server route ([#145](https://github.com/sveltejs/sapper/issues/145))
* Don't serve error page for server route errors ([#138](https://github.com/sveltejs/sapper/issues/138))
## 0.7.5
* Allow dynamic parameters inside route parts ([#139](https://github.com/sveltejs/sapper/issues/139))

View File

@@ -1,6 +1,6 @@
{
"name": "sapper",
"version": "0.7.5",
"version": "0.7.6",
"description": "Military-grade apps, engineered by Svelte",
"main": "middleware.js",
"bin": {

View File

@@ -22,8 +22,11 @@ function generate_client(routes: Route[], src: string, dev: boolean, dev_port?:
// This file is generated by Sapper — do not edit it!
export const routes = [
${routes
.filter(route => route.type === 'page')
.map(route => {
if (route.type !== 'page') {
return `{ pattern: ${route.pattern}, ignore: true }`;
}
const file = posixify(`../../routes/${route.file}`);
if (route.id === '_4xx' || route.id === '_5xx') {

View File

@@ -234,9 +234,21 @@ function get_route_handler(chunks: Record<string, string>, routes: RouteObject[]
};
}
handler(req, res, () => {
handle_not_found(req, res, 404, 'Not found');
});
const handle_error = (err?: Error) => {
if (err) {
console.error(err.stack);
res.statusCode = 500;
res.end(err.message);
} else {
handle_not_found(req, res, 404, 'Not found');
}
};
try {
handler(req, res, handle_error);
} catch (err) {
handle_error(err);
}
} else {
// no matching handler for method — 404
handle_not_found(req, res, 404, 'Not found');
@@ -294,7 +306,7 @@ function get_route_handler(chunks: Record<string, string>, routes: RouteObject[]
}));
}
return function find_route(req: Req, res: ServerResponse, next: () => void) {
return function find_route(req: Req, res: ServerResponse) {
const url = req.pathname;
try {

View File

@@ -26,6 +26,8 @@ function select_route(url: URL): Target {
for (const route of routes) {
const match = route.pattern.exec(url.pathname);
if (match) {
if (route.ignore) return null;
const params = route.params(match);
const query: Record<string, string | true> = {};

View File

@@ -14,7 +14,8 @@ export interface Component {
export type Route = {
pattern: RegExp;
params: (match: RegExpExecArray) => Record<string, string>;
load: () => Promise<{ default: ComponentConstructor }>
load: () => Promise<{ default: ComponentConstructor }>;
ignore?: boolean;
};
export type ScrollPosition = {

View File

@@ -1,4 +1,4 @@
import posts from './_posts.js';
import posts from './blog/_posts.js';
const contents = JSON.stringify(posts.map(post => {
return {

View File

@@ -63,7 +63,7 @@
return this.error(500, 'something went wrong');
}
return fetch(`/api/blog/${slug}`).then(r => {
return fetch(`/blog/${slug}.json`).then(r => {
if (r.status === 200) {
return r.json().then(post => ({ post }));
this.error(r.status, '')

View File

@@ -70,7 +70,7 @@ const posts = [
<ul>
<li>It's powered by <a href='https://svelte.technology'>Svelte</a> instead of React, so it's faster and your apps are smaller</li>
<li>Instead of route masking, we encode route parameters in filenames. For example, the page you're looking at right now is <code>routes/blog/[slug].html</code></li>
<li>As well as pages (Svelte components, which render on server or client), you can create <em>server routes</em> in your <code>routes</code> directory. These are just <code>.js</code> files that export functions corresponding to HTTP methods, and receive Express <code>request</code> and <code>response</code> objects as arguments. This makes it very easy to, for example, add a JSON API such as the one powering this very page (look in <code>routes/api/blog</code>)</li>
<li>As well as pages (Svelte components, which render on server or client), you can create <em>server routes</em> in your <code>routes</code> directory. These are just <code>.js</code> files that export functions corresponding to HTTP methods, and receive Express <code>request</code> and <code>response</code> objects as arguments. This makes it very easy to, for example, add a JSON API such as the one <a href='/blog/how-is-sapper-different-from-next.json'>powering this very page</a></li>
<li>Links are just <code>&lt;a&gt;</code> elements, rather than framework-specific <code>&lt;Link&gt;</code> components. That means, for example, that <a href='/blog/how-can-i-get-involved'>this link right here</a>, despite being inside a blob of HTML, works with the router as you'd expect.</li>
</ul>
`

View File

@@ -32,7 +32,7 @@
},
preload({ params, query }) {
return fetch(`/api/blog/contents`).then(r => r.json()).then(posts => {
return fetch(`/blog.json`).then(r => r.json()).then(posts => {
return { posts };
});
}

View File

@@ -0,0 +1,3 @@
export function get() {
throw new Error('nope');
}

View File

@@ -12,6 +12,10 @@ run('development');
Nightmare.action('page', {
title(done) {
this.evaluate_now(() => document.querySelector('h1').textContent, done);
},
text(done) {
this.evaluate_now(() => document.body.textContent, done);
}
});
@@ -193,7 +197,7 @@ function run(env) {
});
})
.then(requests => {
assert.ok(!!requests.find(r => r.url === '/api/blog/why-the-name'));
assert.ok(!!requests.find(r => r.url === '/blog/why-the-name.json'));
});
});
@@ -219,7 +223,7 @@ function run(env) {
});
})
.then(mouseover_requests => {
assert.ok(mouseover_requests.findIndex(r => r.url === '/api/blog/what-is-sapper') !== -1);
assert.ok(mouseover_requests.findIndex(r => r.url === '/blog/what-is-sapper.json') !== -1);
return capture(() => {
return nightmare
@@ -228,7 +232,7 @@ function run(env) {
});
})
.then(click_requests => {
assert.ok(click_requests.findIndex(r => r.url === '/api/blog/what-is-sapper') === -1);
assert.ok(click_requests.findIndex(r => r.url === '/blog/what-is-sapper.json') === -1);
});
});
@@ -376,6 +380,25 @@ function run(env) {
assert.equal(title, 'Internal server error');
});
});
it('does not attempt client-side navigation to server routes', () => {
return nightmare.goto(`${base}/blog/how-is-sapper-different-from-next`)
.init()
.click(`[href="/blog/how-is-sapper-different-from-next.json"]`)
.wait(200)
.page.text()
.then(text => {
JSON.parse(text);
});
});
it('does not serve error page for non-page errors', () => {
return nightmare.goto(`${base}/throw-an-error`)
.page.text()
.then(text => {
assert.equal(text, 'nope');
});
});
});
describe('headers', () => {
@@ -415,13 +438,13 @@ function run(env) {
'blog/what-is-sapper/index.html',
'blog/why-the-name/index.html',
'api/blog/contents',
'api/blog/a-very-long-post',
'api/blog/how-can-i-get-involved',
'api/blog/how-is-sapper-different-from-next',
'api/blog/how-to-use-sapper',
'api/blog/what-is-sapper',
'api/blog/why-the-name',
'blog.json',
'blog/a-very-long-post.json',
'blog/how-can-i-get-involved.json',
'blog/how-is-sapper-different-from-next.json',
'blog/how-to-use-sapper.json',
'blog/what-is-sapper.json',
'blog/why-the-name.json',
'favicon.png',
'global.css',