./src/index.js annotated source
Back to index1const fs = require('fs');
2
3const config = require('../config.js');
4
5const express = require('express');
6const bodyParser = require('body-parser');
7const app = express();
8app.use(bodyParser.text({
9 limit: '50kb',
10}));
11
12const api = require('./api.js');
13
14const CONTENT_TYPES = {
15 '.js': 'text/javascript',
16 '.html': 'text/html',
17 '.ico': 'image/x-icon',
18}
19
20// STATIC ASSETS
21const STATIC_PATHS = {
22 '/': 'index.html',
23
24 // Editor alias routes
25 '/new': 'editor.html',
26 '/welcome': 'editor.html',
27
28 '/favicon.ico': 'assets/favicon.ico',
29 '/h/:htmlFrameHash/j/:jsFrameHash/edit': 'editor.html',
30}
31const respondWith = (res, static_path) => {
32 fs.readFile(`static/${static_path}`, (err, data) => {
33 if (err) {
34 throw err;
35 }
36
We determine the content-type based on requested resource file ending, and fall back to HTML.
39 let contentType = 'text/html';
40 for (const [ending, type] of Object.entries(CONTENT_TYPES)) {
41 if (static_path.endsWith(ending)) {
42 contentType = type;
43 break;
44 }
45 }
46
47 res.set('Content-Type', contentType);
48 res.send(data);
49 });
50}
51for (const [uri, path] of Object.entries(STATIC_PATHS)) {
52 app.get(uri, (_req, res) => {
53 try {
54 respondWith(res, path);
55 } catch (e) {
56 console.error(e);
57 // For now, assume it's a not-found error
58 respondWith(res, '404.html');
59 }
60 });
61}
62app.use('/static', express.static('static'));
63
Easily redirect /f/*.html/edit
to an editor view to edit statically rendered Codeframes by just appending /edit
to the URL.
66app.get('/f/:htmlFrameHash/:jsFrameHash.html/edit', (req, res) => {
67 res.redirect(302, `/h/${req.params.htmlFrameHash}/j/${req.params.jsFrameHash}/edit`);
68});
69
70// API
71const API_PATHS = {
72 'GET /api/frame/:frameHash': api.frame.get,
73 'POST /api/frame/': api.frame.post,
74
75 'GET /f/:htmlFrameHash/:jsFrameHash.html': api.frame.getPage,
76}
77const METHODS = ['GET', 'POST', 'PUT', 'DELETE'];
78for (const [spec, handler] of Object.entries(API_PATHS)) {
79 const [method, route] = spec.split(' ');
80 let appMethod;
81 if (METHODS.includes(method)) {
82 appMethod = app[method.toLowerCase()].bind(app);
83 } else {
84 throw new Error(`Method ${method} for route ${route} is not valid`);
85 }
86
We determine the content-type based on requested resource file ending, and fall back to JSON.
89 let contentType = 'application/json';
90 for (const [ending, type] of Object.entries(CONTENT_TYPES)) {
91 if (route.endsWith(ending)) {
92 contentType = type;
93 break;
94 }
95 }
96
97 appMethod(route, async (req, res) => {
98 try {
99 res.set('Content-Type', contentType);
100 const result = await handler(req.params, req.query, req.body);
101 if (typeof result === 'string') {
102 res.send(result);
103 } else {
104 res.send(JSON.stringify(result));
105 }
106 } catch (e) {
107 console.error(e);
108 res.send('error');
109 }
110 });
111}
112
113// 404 last
114app.use((_req, res) => respondWith(res, '404.html'));
115
116app.listen(
117 config.PORT,
118 () => console.log(`Codeframe running on localhost:${config.PORT}`),
119);
120