-
Notifications
You must be signed in to change notification settings - Fork 26
Expand file tree
/
Copy pathserver.js
More file actions
290 lines (261 loc) 路 10.4 KB
/
Copy pathserver.js
File metadata and controls
290 lines (261 loc) 路 10.4 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
/**
* Node.js Web Server for Examples
*
* Some of the examples will work by simply opening them as files in a browser,
* however some of the examples require a web server, this file provides the
* needed web server and can be used with any of the examples. Language Translation
* files [{file}.{lang}.json] require a local web server as they are loaded from
* [fetch]. By default when files are loaded directly then only English will be
* displayed.
*
* This has no dependencies outside of built-in Node.js objects and the
* [app.js] file; see additional comments in [app.js].
*
* To run:
* 1) Install node.js
* https://nodejs.org/en/
* 2) Open a Command Line (Terminal, Cmd, etc)
* 3) Change directory to where this file is located:
* cd {dir}
* 4) Run with node:
* node server.js
* 5) Open in browser to view examples:
* http://127.0.0.1:8080/
* 6) Additionally if you use a popular code editor or IDE you can
* likely start this script directly from your editor.
*/
/* Validates online with both [jshint] and [eslint] */
/* Select [ECMA Version] = 2018 for [eslint] */
/* jshint esversion:8, node:true */
/* eslint-env node, es6 */
'use strict';
const app = require('./../server/app.js');
const path = require('path');
const url = require('url');
const fs = require('fs');
const util = require('util');
const readdir = util.promisify(fs.readdir);
const readFile = util.promisify(fs.readFile);
const port = 8080;
async function getFiles(path, type, keepExt = false) {
return await readdir(path)
.then(files => {
return files.filter(f => f.endsWith(type) && !f.startsWith('_'));
})
.then(files => {
if (keepExt) {
return files;
}
return files.map(f => f.replace(type, ''));
})
.catch(err => {
throw err;
});
}
app.get('/', async (req, res) => {
const filePath = path.join(__dirname, `html/npm-start-page.htm`);
let html = await readFile(filePath, 'utf8');
res.html(html);
});
// Make sure the `favicon.ico` and other basic URL's are handled before the '/:file' route
app.get('/favicon.ico', (req, res) => {
const filePath = path.join(__dirname, 'favicon.ico');
res.file(filePath);
});
app.get('/LICENSE', async (req, res) => {
const filePath = path.join(__dirname, '..', 'LICENSE');
const text = await readFile(filePath, 'utf8');
res.text(text);
});
app.get('/examples', async (req, res) => {
const examples = [];
// Get Hello World Examples
let files = await getFiles(path.join(__dirname, 'hello-world'), '.htm');
files.forEach(file => {
const label = 'hello world ' + file.replace(/-/g, ' ');
examples.push({
url: `/examples/hello-world/en/${file}.htm`,
label: label,
})
});
// Get all Examples from this dir
files = await getFiles(__dirname, '.htm');
files.push('https://awesome-web-react.js.org/');
files.forEach(file => {
const label = (file.startsWith('https://') ? file : file.replace(/-/g, ' '));
examples.push({
url: file,
label: label,
})
});
// Send response
res.json({ examples });
});
app.get('/docs', async (req, res) => {
const rootDir = path.join(__dirname, '..');
const dirs = [
{ path: rootDir, type: '.md' },
{ path: path.join(rootDir, 'docs'), type: '.md' },
{ path: path.join(rootDir, 'docs'), type: '.txt' },
{ path: path.join(rootDir, 'docs', 'i18n-readme'), type: '.md' },
];
const docs = [];
for (const item of dirs) {
const dirPath = item.path;
const files = await getFiles(dirPath, item.type, true);
files.forEach(file => {
docs.push({
file: file,
dir: dirPath.replace(rootDir, ''),
});
});
}
// Send response
res.json({ docs });
});
app.get('/doc', (req, res) => {
const qs = url.parse(req.url, true).query;
const filePath = path.join(__dirname, `..`, qs.dir, qs.file);
res.file(filePath);
});
app.get('/:file', (req, res, file) => {
// Get file Path
const fileName = (file + (file.includes('.') ? '' : '.htm'));
const filePath = path.join(__dirname, fileName);
// Example of using CSP (Content Security Policy) with DataFormsJS.
// All examples should work with the following settings on modern browsers.
// CSP is specified as a Response Header on the root HTML file.
//
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy
//
const srcScr = [
// 'unsafe-eval' is needed for scripts because Vue, Handlebars, and other templating
// engines dynamically create JavaScript functions based on the template code.
"'unsafe-eval'",
// If using the React [jsxLoader.js] JSX code is compiled and added as inline <script>
// elements on the page so using 'unsafe-inline' is the easiest method to allow this.
// When not including it sha256 hashes can be used as shown below. Hashes can be used
// with the standard framework on most examples that use the simple `app.LazyLoad`
// code snippet, however with React many browsers would have to be tested in order
// to get all the hashes so 'unsafe-inline' is recommend for React.
"'unsafe-inline'",
// URL's
'https://cdn.jsdelivr.net',
'https://stackpath.bootstrapcdn.com',
'https://code.jquery.com',
'https://dataformsjs.s3-us-west-1.amazonaws.com',
'https://d2xbd92kui7v97.cloudfront.net',
'https://*.dataformsjs.com',
'https://unpkg.com',
'https://polyfill.io',
// Hashes - These can be determined from Chrome/Edge DevTools
// "'sha256-7xY3owh8hUdTNBs11onjKib0EBNTfow+rGLO39veZHU='",
// "'sha256-+dVYzlB/eDQj/kya4BJI/ql82f/IjfR8xcB7+TogTDY='",
// "'sha256-iXxEc1Pm8vLfjpVYOx2o1fcXhvjeDowwn9Fih34GVZ4='",
// "'sha256-bLQBlv/aRvakcGB+LhnjxeuV+lu39E+irE7eLmldnoQ='",
// "'sha256-O6TgiUsiQiSWa8MscEueffR7zWcXl+VUkkXlNVuTOCY='",
// "'sha256-wh85FoQaAlZMEYm7+dB+o3A7fGWR/xAjFjCF9ZYaUkU='",
// "'sha256-xR/1Z4tZPAL+xY/QYfJaC6j9B1IKTmKVJy18lLBF0nQ='",
// "'sha256-9jHiyRplSgxUACLlnPdz1b4y/GKWcqHcfRPfCbJKzSg='",
// "'sha256-RW/de6kIdWa5bf6iYKMWUI3yLEuuSRHgDeHVgF9gj7E='",
];
// For styles a number of DataFormsJS plugins and controls will dynamically
// add <style> elements to the page <head>. For example all pages that use
// Vue and all Image Gallery Code. In order for this to work 'unsafe-inline'
// is needed on [style-src].
const styleSrc = [
'https:',
"'unsafe-inline'",
];
const connectSrc = [
'https://*.dataformsjs.com',
'https://hacker-news.firebaseio.com',
'https://dataformsjs.s3-us-west-1.amazonaws.com',
'https://d2xbd92kui7v97.cloudfront.net',
'http://localhost:3000',
'https://raw.githubusercontent.com',
];
const imageSrc = [
'https:',
'data:',
'blob:',
'http://*.openstreetmap.org',
];
const fontSrc = [
'https://fonts.gstatic.com/',
'https://cdn.jsdelivr.net',
];
const workerSrc = [
'blob:',
];
const csp = [
"default-src 'self'",
`script-src 'self' ${srcScr.join(' ')}`,
`style-src 'self' ${styleSrc.join(' ')}`,
`img-src 'self' ${imageSrc.join(' ')}`,
`connect-src 'self' ${connectSrc.join(' ')}`,
`font-src 'self' ${fontSrc.join(' ')}`,
`worker-src 'self' ${workerSrc.join(' ')}`,
].join('; ');
res.setHeader('Content-Security-Policy', csp);
// Send the file
res.file(filePath);
});
// To keep hello world files simple (when viewing source from a browser)
// the js plugin [i18n] is not used, rather the content of the file is updated
// using find/replace with i18n key values before sending to the client.
//
// http://127.0.0.1:8080/examples/hello-world/en/hbs.htm
// http://127.0.0.1:8080/examples/hello-world/en/vue.htm
// http://127.0.0.1:8080/examples/hello-world/en/js.htm
// http://127.0.0.1:8080/examples/hello-world/en/web.htm
// http://127.0.0.1:8080/examples/hello-world/en/web-url-router.htm
// http://127.0.0.1:8080/examples/hello-world/en/react.htm
// http://127.0.0.1:8080/examples/hello-world/en/preact.htm
// http://127.0.0.1:8080/examples/hello-world/en/rax.htm
// http://127.0.0.1:8080/examples/hello-world/en/hyperapp.htm
// http://127.0.0.1:8080/examples/hello-world/en/vue3-with-jsx.htm
app.get('/examples/hello-world/:lang/:file', async (req, res, lang, file) => {
// CSS or SVG file
if (!file.endsWith('.htm')) {
const filePath = path.join(__dirname, `hello-world/${file}`);
res.file(filePath);
return;
}
// HTML File
// NOTE - to properly handle missing languages or files the `await readFile` calls
// would need to be wrapped in a `try {...} catch {...}` and handled with `res.pageNotFound`
//
// 1) First read the i18n file: [dataformsjs\examples\i18n\hello-world.{lang}.json]
const langPath = path.join(__dirname, `i18n/hello-world.${lang}.json`);
const json = await readFile(langPath, 'utf8');
const i18n = JSON.parse(json);
i18n.lang = lang;
// 2) Read html file: [dataformsjs\examples\hello-world\{file}]
const htmlPath = path.join(__dirname, `hello-world/${file}`);
const content = await readFile(htmlPath, 'utf8');
// 3) Replace all i18n keys in the file
let html = content;
for (const key of Object.keys(i18n)) {
html = html.replace(new RegExp('{{' + key + '}}', 'g'), i18n[key]);
}
// 4) Send HTML content
res.html(html);
});
app.get('/js/:file', (req, res, file) => {
const filePath = path.join(__dirname, '..', 'js', decodeURIComponent(file));
res.file(filePath);
});
app.get('/js/:dir/:file', (req, res, dir, file) => {
const filePath = path.join(__dirname, '..', 'js', decodeURIComponent(dir), decodeURIComponent(file));
res.file(filePath);
});
app.get('/js/:dir1/:dir2/:file', (req, res, dir1, dir2, file) => {
const filePath = path.join(__dirname, '..', 'js', decodeURIComponent(dir1), decodeURIComponent(dir2), decodeURIComponent(file));
res.file(filePath);
});
app.get('/vendor/:file', (req, res, file) => {
const filePath = path.join(__dirname, '..', 'vendor', decodeURIComponent(file));
res.file(filePath);
});
app.run(port, __dirname);