-
Notifications
You must be signed in to change notification settings - Fork 467
Expand file tree
/
Copy pathdev-server.mjs
More file actions
141 lines (120 loc) · 3.75 KB
/
dev-server.mjs
File metadata and controls
141 lines (120 loc) · 3.75 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
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
import esbuild from 'esbuild';
import http from 'http';
import fs from 'fs';
import path from 'path';
// Headers matching res/_headers
const EXTRA_HEADERS = {
'X-Content-Type-Options': 'nosniff',
'X-XSS-Protection': '1; mode=block',
'Referrer-Policy': 'same-origin',
'Content-Security-Policy': [
"default-src 'self'",
"script-src 'self' 'wasm-unsafe-eval'",
"style-src 'self' 'unsafe-inline' https://fonts.googleapis.com",
"font-src 'self' https://fonts.gstatic.com",
'img-src http: https: data:',
"object-src 'none'",
'connect-src *',
"form-action 'none'",
].join('; '),
};
// Allowed hosts for dev server
const BASE_ALLOWED_HOSTS = ['localhost', '.app.github.dev'];
function isHostAllowed(hostHeader, host) {
if (!hostHeader) {
return false;
}
// When binding to all interfaces, allow any host.
if (host === '0.0.0.0') {
return true;
}
const hostname = hostHeader.split(':')[0];
// Include the configured host in addition to the defaults.
const allowedHosts = BASE_ALLOWED_HOSTS.includes(host)
? BASE_ALLOWED_HOSTS
: [...BASE_ALLOWED_HOSTS, host];
return allowedHosts.some((allowedHost) => {
if (allowedHost.startsWith('.')) {
return hostname.endsWith(allowedHost);
}
return hostname === allowedHost;
});
}
export async function startDevServer(buildConfig, options = {}) {
const {
port = 4242,
host = 'localhost',
distDir = 'dist',
fallback = 'index.html',
onServerStart,
cleanDist = true,
} = options;
// Clean dist directory first
if (cleanDist && fs.existsSync(distDir)) {
fs.rmSync(distDir, { recursive: true });
}
// Create build context for watching
const buildContext = await esbuild.context(buildConfig);
const { hosts, port: esbuildServerPort } = await buildContext.serve({
host: '127.0.0.1',
servedir: distDir,
fallback: fallback ? path.join(distDir, fallback) : undefined,
});
const hostname = hosts[0];
// Start watching for changes
await buildContext.watch();
// Create HTTP server
const server = http.createServer((req, res) => {
// Validate Host header
if (!isHostAllowed(req.headers.host, host)) {
res.writeHead(403, { 'Content-Type': 'text/plain' });
res.end('Invalid Host header');
return;
}
const requestOptions = {
hostname,
port: esbuildServerPort,
path: req.url,
method: req.method,
headers: req.headers,
};
// Forward each incoming request to esbuild
const proxyReq = http.request(requestOptions, (proxyRes) => {
// Add security headers to the response
const responseHeaders = {
...proxyRes.headers,
...EXTRA_HEADERS,
};
// Forward the response from esbuild to the client
// Note: esbuild's serve with fallback handles 404s
// by serving index.html for client-side routing
res.writeHead(proxyRes.statusCode, responseHeaders);
proxyRes.pipe(res, { end: true });
});
// Forward the body of the request to esbuild
req.pipe(proxyReq, { end: true });
});
// Start the server
await new Promise((resolve) => {
server.listen(port, host, () => {
if (onServerStart) {
onServerStart(`http://${host}:${port}`);
}
resolve();
});
});
// Graceful shutdown
let isShuttingDown = false;
process.on('SIGINT', async () => {
if (isShuttingDown) return;
isShuttingDown = true;
console.log('\nShutting down...');
await buildContext.dispose();
server.close();
process.exit(0);
});
return { server, buildContext };
}