Skip to content

Commit 65b3058

Browse files
committed
server
0 parents  commit 65b3058

File tree

7 files changed

+424
-0
lines changed

7 files changed

+424
-0
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
node_modules

github_ribbon.png

8.14 KB
Loading

index.html

Lines changed: 183 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,183 @@
1+
<!DOCTYPE html>
2+
<html>
3+
<head>
4+
<link rel="stylesheet" href="./style.css">
5+
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.48.2/codemirror.min.css">
6+
</head>
7+
<body>
8+
<dialog id="loading">
9+
Loading...
10+
</dialog>
11+
<script>
12+
loading.showModal();
13+
window.addEventListener('error', () => {
14+
loading.close();
15+
});
16+
</script>
17+
<!-- github ribbon -->
18+
<a id="github" href="https://github.com/engine262/engine262" aria-label="Fork me on GitHub"></a>
19+
<h1>engine262</h1>
20+
<p>A self-hosted JavaScript engine for development and exploration.</p>
21+
22+
<main>
23+
<section id="input-section">
24+
<fieldset><legend>Features</legend>
25+
<ul id="featureList"></ul>
26+
</fieldset>
27+
<fieldset><legend>Editor</legend>
28+
<label><input id="autoevaluate" checked type="checkbox">Auto Evaluate</label>
29+
<select id="mode">
30+
<option value="script" selected="selected" autocomplete="off">Script</option>
31+
<option value="module">Module</option>
32+
</select>
33+
</fieldset>
34+
35+
<textarea id="input" autocomplete="off"></textarea>
36+
<button type="button" id="run">Evaluate</button>
37+
</section>
38+
39+
<section id="output-section">
40+
<h2>Output:</h2>
41+
42+
<textarea id="output" cols="80" autocomplete="off" disabled></textarea>
43+
</section>
44+
</main>
45+
46+
<script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.48.2/codemirror.min.js"></script>
47+
<script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.48.2/mode/javascript/javascript.min.js"></script>
48+
<script src="https://unpkg.com/[email protected]/libs/base64-string.js"></script>
49+
<script src="https://unpkg.com/[email protected]/libs/lz-string.min.js"></script>
50+
<script>
51+
const parameters = new URLSearchParams(document.location.hash.slice(1));
52+
function saveState() {
53+
const state = new URLSearchParams();
54+
if (parameters.has('gist')) {
55+
state.set('gist', parameters.get('gist'));
56+
} else {
57+
state.set('code', LZString.compressToBase64(editor.getValue()));
58+
}
59+
state.set('features', [...features].join(','));
60+
state.set('mode', mode.value);
61+
document.location.hash = state.toString();
62+
}
63+
64+
let featuresPopulated = false;
65+
const features = new Set(parameters.has('features') ? parameters.get('features').split(',') : []);
66+
let worker;
67+
68+
if (parameters.has('mode')) {
69+
mode.value = parameters.get('mode');
70+
}
71+
72+
function respawn() {
73+
if (worker) {
74+
worker.terminate();
75+
}
76+
worker = new Worker('./worker.js');
77+
worker.addEventListener('message', ({ data }) => {
78+
if (data.log) {
79+
console.log(...data.log);
80+
data.log.forEach((l) => {
81+
output.value += l;
82+
output.value += ' ';
83+
});
84+
output.value += '\n';
85+
} else if (data.error) {
86+
console.error(data.error);
87+
output.value += data.error;
88+
output.value += '\n';
89+
} else if (data.FEATURES) {
90+
if (!featuresPopulated) {
91+
featuresPopulated = true;
92+
data.FEATURES
93+
.forEach(({ name }) => {
94+
const li = document.createElement('li');
95+
const input = document.createElement('input');
96+
input.type = 'checkbox';
97+
input.checked = features.has(name);
98+
input.addEventListener('click', () => {
99+
if (input.checked) {
100+
features.add(name);
101+
} else {
102+
features.delete(name);
103+
}
104+
saveState();
105+
respawn();
106+
runEvaluation();
107+
});
108+
const label = document.createElement('label');
109+
label.appendChild(input);
110+
const txt = document.createTextNode(name);
111+
label.appendChild(txt);
112+
li.appendChild(label);
113+
featureList.appendChild(li);
114+
});
115+
}
116+
}
117+
});
118+
worker.postMessage({ features: [...features], mode: mode.value });
119+
}
120+
121+
mode.addEventListener('change', () => {
122+
saveState();
123+
respawn();
124+
runEvaluation();
125+
});
126+
127+
respawn();
128+
129+
let onChangeTimer = null;
130+
const editor = CodeMirror.fromTextArea(input, {
131+
lineNumbers: true,
132+
mode: 'javascript',
133+
});
134+
135+
function runEvaluation() {
136+
output.value = '';
137+
saveState();
138+
worker.postMessage({ input: editor.getValue() });
139+
}
140+
141+
editor.on('change', () => {
142+
if (!autoevaluate.checked) return;
143+
if (onChangeTimer !== null) {
144+
clearTimeout(onChangeTimer);
145+
}
146+
onChangeTimer = setTimeout(
147+
() => {
148+
onChangeTimer = null;
149+
runEvaluation();
150+
},
151+
500
152+
);
153+
});
154+
155+
if (parameters.has('code')) {
156+
editor.setValue(LZString.decompressFromBase64(parameters.get('code')));
157+
} else if (parameters.has('gist')) {
158+
fetch(`https://api.github.com/gists/${parameters.get('gist')}`)
159+
.then((r) => r.json())
160+
.then((data) => {
161+
const fileName = Object.keys(data.files)[0];
162+
const file = data.files[fileName];
163+
editor.setValue(file.content);
164+
if (fileName.endsWith('.js')) {
165+
mode.value = 'script';
166+
} else if (fileName.endsWith('.mjs')) {
167+
mode.value = 'module';
168+
}
169+
saveState();
170+
respawn();
171+
});
172+
} else {
173+
editor.setValue('print(\'Hello, World!\');');
174+
}
175+
176+
run.addEventListener('click', () => {
177+
runEvaluation();
178+
});
179+
180+
loading.close();
181+
</script>
182+
</body>
183+
</html>

server/index.js

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
'use strict';
2+
3+
const http = require('http');
4+
const fetch = require('node-fetch');
5+
6+
const AUTH = `Basic ${Buffer.from(`engine262-bot:${process.env.GH_TOKEN}`).toString('base64')}`;
7+
8+
const EXTENSIONS = {
9+
script: 'js',
10+
module: 'mjs',
11+
__proto__: null,
12+
};
13+
14+
function createGist(content, mode) {
15+
const name = `code.${EXTENSIONS[mode]}`;
16+
return fetch('https://api.github.com/gists', {
17+
method: 'POST',
18+
headers: {
19+
'Authorization': AUTH,
20+
'Content-Type': 'application/json',
21+
},
22+
body: JSON.stringify({
23+
files: { [name]: { content } },
24+
description: 'Code shared from https://engine262.js.org',
25+
public: false,
26+
}),
27+
})
28+
.then((r) => r.json());
29+
}
30+
31+
const server = http.createServer((req, res) => {
32+
if (req.headers.origin !== 'https://engine262.js.org') {
33+
res.writeHead(403);
34+
res.end('403');
35+
return;
36+
}
37+
if (req.url !== '/gist') {
38+
res.writeHead(404);
39+
res.end('404');
40+
return;
41+
}
42+
if (req.method !== 'POST') {
43+
res.writeHead(405);
44+
res.end('405');
45+
return;
46+
}
47+
let body = '';
48+
req.on('data', (chunk) => {
49+
body += chunk;
50+
});
51+
req.on('end', () => {
52+
const { content, mode } = JSON.parse(body);
53+
createGist(content, mode)
54+
.then((data) => {
55+
res.writeHead(200, { 'Content-Type': 'application/json' });
56+
res.end(JSON.stringify(data));
57+
}, (e) => {
58+
console.error(e); // eslint-disable-line no-console
59+
res.writeHead(500);
60+
res.end('500');
61+
});
62+
});
63+
});
64+
65+
server.listen(5000);

server/package.json

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
{
2+
"name": "server",
3+
"version": "1.0.0",
4+
"description": "",
5+
"main": "index.js",
6+
"scripts": {
7+
"start": "node index.js"
8+
},
9+
"author": "",
10+
"license": "MIT",
11+
"dependencies": {
12+
"node-fetch": "^2.6.0"
13+
}
14+
}

style.css

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
html, body {
2+
padding: 0;
3+
margin: 0;
4+
overflow: auto;
5+
background-color: #f7df1e;
6+
display: flex;
7+
flex-direction: column;
8+
}
9+
h1, h2 {
10+
margin: 0;
11+
}
12+
label {
13+
white-space: nowrap;
14+
}
15+
fieldset {
16+
border: none;
17+
padding: 0;
18+
margin: 0;
19+
margin-bottom: 5px;
20+
}
21+
legend {
22+
font-weight: bold;
23+
margin-bottom: 5px;
24+
}
25+
body {
26+
font-family: sans-serif;
27+
padding: 10px;
28+
display: flex;
29+
flex-direction: column;
30+
}
31+
main {
32+
display: flex;
33+
flex-direction: column;
34+
flex-grow: 1;
35+
}
36+
textarea {
37+
font-family: monospace;
38+
display: block;
39+
flex-grow: 1;
40+
border: 1px solid black;
41+
}
42+
.CodeMirror {
43+
flex-grow: 1;
44+
border: 1px solid black;
45+
}
46+
textarea:disabled {
47+
border: 1px solid black;
48+
color: black;
49+
}
50+
#input-section, #output-section {
51+
display: flex;
52+
flex-direction: column;
53+
flex-grow: 1;
54+
flex-shrink: 1;
55+
}
56+
#output {
57+
flex-grow: 1;
58+
min-height: 200px;
59+
}
60+
#featureList {
61+
margin: 0;
62+
padding: 0;
63+
list-style: none;
64+
display: flex;
65+
flex-direction: row;
66+
flex-wrap: wrap;
67+
}
68+
label {
69+
margin-right: 1ex;
70+
margin-bottom: 5px;
71+
}
72+
#github {
73+
display: block;
74+
position: absolute;
75+
top: 0;
76+
right: 0;
77+
width: 149px;
78+
height: 149px;
79+
background-image: url('./github_ribbon.png');
80+
}
81+
@media (min-width: 800px) {
82+
html, body {
83+
height: 100%;
84+
}
85+
main {
86+
flex-direction: row;
87+
}
88+
main > *:not(:first-child) {
89+
margin-left: 10px;
90+
}
91+
#featureList {
92+
flex-wrap: wrap;
93+
}
94+
}

0 commit comments

Comments
 (0)