108 lines
3.3 KiB
JavaScript
108 lines
3.3 KiB
JavaScript
|
|
import { createReadStream, statSync } from "node:fs";
|
||
|
|
import { createServer } from "node:http";
|
||
|
|
import { spawn } from "node:child_process";
|
||
|
|
import { dirname, extname, join, normalize } from "node:path";
|
||
|
|
import { fileURLToPath } from "node:url";
|
||
|
|
|
||
|
|
const projectRoot = dirname(fileURLToPath(import.meta.url));
|
||
|
|
const webRoot = join(projectRoot, "web");
|
||
|
|
const shouldOpenBrowser = !process.argv.includes("--no-open");
|
||
|
|
|
||
|
|
const contentTypes = {
|
||
|
|
".css": "text/css; charset=utf-8",
|
||
|
|
".html": "text/html; charset=utf-8",
|
||
|
|
".ico": "image/x-icon",
|
||
|
|
".js": "text/javascript; charset=utf-8",
|
||
|
|
".json": "application/json; charset=utf-8",
|
||
|
|
".pak": "application/octet-stream",
|
||
|
|
".png": "image/png",
|
||
|
|
".wasm": "application/wasm",
|
||
|
|
".woff2": "font/woff2",
|
||
|
|
};
|
||
|
|
|
||
|
|
function localPath(url) {
|
||
|
|
const pathname = decodeURIComponent(new URL(url, "http://127.0.0.1").pathname);
|
||
|
|
const requested = pathname === "/" ? "/apps/ep-sample-tool/" : pathname;
|
||
|
|
const relative = normalize(requested).replace(/^([/\\])+/, "");
|
||
|
|
const candidate = join(webRoot, relative);
|
||
|
|
if (!candidate.startsWith(webRoot)) return null;
|
||
|
|
|
||
|
|
try {
|
||
|
|
return statSync(candidate).isDirectory() ? join(candidate, "index.html") : candidate;
|
||
|
|
} catch {
|
||
|
|
return candidate;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
const server = createServer((request, response) => {
|
||
|
|
// The upstream app reports errors to /_api/err. Keep that diagnostic local.
|
||
|
|
if (request.url?.startsWith("/_api/err")) {
|
||
|
|
response.writeHead(204).end();
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
const path = localPath(request.url ?? "/");
|
||
|
|
if (!path) {
|
||
|
|
response.writeHead(403).end("Forbidden");
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
try {
|
||
|
|
const { size } = statSync(path);
|
||
|
|
response.writeHead(200, {
|
||
|
|
"Cache-Control": "no-store",
|
||
|
|
"Content-Length": size,
|
||
|
|
"Content-Type": contentTypes[extname(path)] ?? "application/octet-stream",
|
||
|
|
"Cross-Origin-Opener-Policy": "same-origin",
|
||
|
|
"X-Content-Type-Options": "nosniff",
|
||
|
|
});
|
||
|
|
createReadStream(path).pipe(response);
|
||
|
|
} catch {
|
||
|
|
response.writeHead(404, { "Content-Type": "text/plain; charset=utf-8" });
|
||
|
|
response.end("Not found");
|
||
|
|
}
|
||
|
|
});
|
||
|
|
|
||
|
|
server.listen(0, "127.0.0.1", () => {
|
||
|
|
const address = server.address();
|
||
|
|
if (!address || typeof address === "string") throw new Error("Could not start local server");
|
||
|
|
|
||
|
|
const url = `http://127.0.0.1:${address.port}/apps/ep-sample-tool/`;
|
||
|
|
if (!shouldOpenBrowser) {
|
||
|
|
console.log(`READY ${url}`);
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
const candidates = [
|
||
|
|
"/Applications/Google Chrome.app/Contents/MacOS/Google Chrome",
|
||
|
|
"/Applications/Chromium.app/Contents/MacOS/Chromium",
|
||
|
|
"/Applications/Microsoft Edge.app/Contents/MacOS/Microsoft Edge",
|
||
|
|
];
|
||
|
|
const browser = candidates.find((candidate) => {
|
||
|
|
try {
|
||
|
|
return statSync(candidate).isFile();
|
||
|
|
} catch {
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
});
|
||
|
|
|
||
|
|
if (!browser) {
|
||
|
|
console.error("Google Chrome, Chromium, or Microsoft Edge is required for Web MIDI.");
|
||
|
|
console.error(`Open this URL in a Web MIDI browser: ${url}`);
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
spawn(browser, [`--app=${url}`, "--new-window"], {
|
||
|
|
detached: true,
|
||
|
|
stdio: "ignore",
|
||
|
|
}).unref();
|
||
|
|
|
||
|
|
console.log("EP sample tool is running fully locally.");
|
||
|
|
console.log(`Local address: ${url}`);
|
||
|
|
console.log("Keep this window open while using the tool. Press Control-C to stop.");
|
||
|
|
});
|
||
|
|
|
||
|
|
for (const signal of ["SIGINT", "SIGTERM"]) {
|
||
|
|
process.on(signal, () => server.close(() => process.exit(0)));
|
||
|
|
}
|