mirror of
https://github.com/HeyPuter/browser.js.git
synced 2026-11-13 18:33:58 -10:00
220 lines
5.6 KiB
TypeScript
220 lines
5.6 KiB
TypeScript
import { createServer } from "vite";
|
|
import fs from "node:fs/promises";
|
|
import { stdout } from "node:process";
|
|
import chalk from "chalk";
|
|
import { execSync } from "node:child_process";
|
|
import http from "node:http";
|
|
import path from "node:path";
|
|
import { createReadStream } from "node:fs";
|
|
import rspackConfig from "./rspack.config.ts";
|
|
import { server as wisp } from "@mercuryworkshop/wisp-js/server";
|
|
import {
|
|
black,
|
|
printBanner,
|
|
resetSuccessLog,
|
|
runRspack,
|
|
normalizeWebsocketUrl,
|
|
warnOnUrlEscape,
|
|
} from "./packages/scramjet/devlib.ts";
|
|
|
|
const image = await fs.readFile("./assets/icon.png");
|
|
|
|
const commit = execSync("git rev-parse --short HEAD", {
|
|
encoding: "utf-8",
|
|
}).replace(/\r?\n|\r/g, "");
|
|
const branch = execSync("git rev-parse --abbrev-ref HEAD", {
|
|
encoding: "utf-8",
|
|
}).replace(/\r?\n|\r/g, "");
|
|
const packagejson = JSON.parse(
|
|
await fs.readFile("./packages/scramjet/package.json", "utf-8")
|
|
);
|
|
const version = packagejson.version;
|
|
|
|
const CHROME_PORT = process.env.CHROME_PORT || 6767;
|
|
const WISP_PORT = process.env.WISP_PORT || 6768;
|
|
const ISOLATION_PORT = process.env.ISOLATION_PORT || 5233;
|
|
|
|
const puterBranding = Boolean(process.env.VITE_PUTER_BRANDING);
|
|
|
|
if (puterBranding) {
|
|
process.env.VITE_ISOLATION_ORIGIN ||= `https://puter.zone`;
|
|
} else {
|
|
if (process.env.VITE_WISP_URL) {
|
|
process.env.VITE_WISP_URL = normalizeWebsocketUrl(
|
|
process.env.VITE_WISP_URL
|
|
);
|
|
} else {
|
|
process.env.VITE_WISP_URL = `ws://localhost:${WISP_PORT}/`;
|
|
}
|
|
process.env.VITE_ISOLATION_ORIGIN ||= `http://localhost:${ISOLATION_PORT}`;
|
|
}
|
|
|
|
const sandboxRoot = path.resolve("./packages/sandbox");
|
|
|
|
const wispserver = http.createServer((req, res) => {
|
|
res.writeHead(200, { "Content-Type": "text/plain" });
|
|
res.end("wisp server js rewrite");
|
|
});
|
|
|
|
wispserver.on("upgrade", (req, socket, head) => {
|
|
wisp.routeRequest(req, socket, head);
|
|
});
|
|
|
|
wispserver.listen(Number(WISP_PORT));
|
|
|
|
const server = await createServer({
|
|
configFile: "./packages/chrome/vite.config.ts",
|
|
root: "./packages/chrome",
|
|
server: {
|
|
port: Number(CHROME_PORT),
|
|
strictPort: true,
|
|
},
|
|
});
|
|
|
|
warnOnUrlEscape(server);
|
|
|
|
await server.listen();
|
|
|
|
wisp.options.allow_loopback_ips = true;
|
|
wisp.options.allow_private_ips = true;
|
|
|
|
const accent = (text: string) => chalk.hex("#4799f1").bold(text);
|
|
const highlight = (text: string) => chalk.hex("#ffffff").bold(text);
|
|
const urlColor = (text: string) => chalk.hex("#64DFDF").underline(text);
|
|
const note = (text: string) => chalk.hex("#CDB4DB")(text);
|
|
const connector = chalk.hex("#8D99AE").dim("@");
|
|
|
|
const lines = [
|
|
black()(`${highlight("BROWSER.JS DEV SERVER")}`),
|
|
black()(
|
|
`${accent("chrome")} ${connector} ${urlColor(
|
|
`http://localhost:${CHROME_PORT}/`
|
|
)}`
|
|
),
|
|
black()(
|
|
`${accent("wisp")} ${connector} ${urlColor(
|
|
process.env.VITE_WISP_URL ?? "(puter)"
|
|
)}`
|
|
),
|
|
black()(
|
|
`${accent("isolation zone")} ${connector} ${urlColor(
|
|
process.env.VITE_ISOLATION_ORIGIN ?? ""
|
|
)}`
|
|
),
|
|
black()(chalk.dim(`[${branch}] ${commit} browserjs/${version}`)),
|
|
];
|
|
|
|
printBanner(image, lines);
|
|
|
|
const MIME_TYPES: Record<string, string> = {
|
|
".html": "text/html; charset=utf-8",
|
|
".htm": "text/html; charset=utf-8",
|
|
".js": "application/javascript; charset=utf-8",
|
|
".mjs": "application/javascript; charset=utf-8",
|
|
".css": "text/css; charset=utf-8",
|
|
".json": "application/json; charset=utf-8",
|
|
".png": "image/png",
|
|
".jpg": "image/jpeg",
|
|
".jpeg": "image/jpeg",
|
|
".gif": "image/gif",
|
|
".svg": "image/svg+xml",
|
|
".ico": "image/x-icon",
|
|
".txt": "text/plain; charset=utf-8",
|
|
};
|
|
|
|
function getMimeType(filePath: string) {
|
|
const ext = path.extname(filePath).toLowerCase();
|
|
return MIME_TYPES[ext] || "application/octet-stream";
|
|
}
|
|
|
|
async function resolveSandboxPath(pathname: string) {
|
|
let normalizedPath = decodeURIComponent(pathname);
|
|
if (normalizedPath.endsWith("/")) {
|
|
normalizedPath += "index.html";
|
|
}
|
|
if (normalizedPath === "/") {
|
|
normalizedPath = "/index.html";
|
|
}
|
|
const resolvedPath = path.resolve(sandboxRoot, `.${normalizedPath}`);
|
|
if (!resolvedPath.startsWith(sandboxRoot)) {
|
|
return null;
|
|
}
|
|
|
|
try {
|
|
const stat = await fs.stat(resolvedPath);
|
|
if (stat.isDirectory()) {
|
|
const indexPath = path.join(resolvedPath, "index.html");
|
|
try {
|
|
await fs.access(indexPath);
|
|
return indexPath;
|
|
} catch {
|
|
return null;
|
|
}
|
|
}
|
|
return resolvedPath;
|
|
} catch {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
const staticServer = http.createServer((req, res) => {
|
|
void (async () => {
|
|
const method = req.method ?? "GET";
|
|
if (method !== "GET" && method !== "HEAD") {
|
|
res.statusCode = 405;
|
|
res.setHeader("Allow", "GET, HEAD");
|
|
res.end("Method Not Allowed");
|
|
return;
|
|
}
|
|
|
|
const requestUrl = new URL(req.url ?? "/", "http://localhost");
|
|
const filePath = await resolveSandboxPath(requestUrl.pathname);
|
|
if (!filePath) {
|
|
res.statusCode = 404;
|
|
res.end("Not Found");
|
|
return;
|
|
}
|
|
|
|
try {
|
|
const mimeType = getMimeType(filePath);
|
|
res.statusCode = 200;
|
|
res.setHeader("Content-Type", mimeType);
|
|
if (method === "HEAD") {
|
|
res.end();
|
|
return;
|
|
}
|
|
|
|
const stream = createReadStream(filePath);
|
|
stream.on("error", () => {
|
|
if (!res.headersSent) {
|
|
res.statusCode = 500;
|
|
}
|
|
res.end("Internal Server Error");
|
|
});
|
|
stream.pipe(res);
|
|
} catch (error) {
|
|
console.error("Failed to serve sandbox asset", error);
|
|
if (!res.headersSent) {
|
|
res.statusCode = 500;
|
|
}
|
|
res.end("Internal Server Error");
|
|
}
|
|
})().catch((error) => {
|
|
console.error("Unhandled sandbox request error", error);
|
|
if (!res.headersSent) {
|
|
res.statusCode = 500;
|
|
}
|
|
res.end("Internal Server Error");
|
|
});
|
|
});
|
|
|
|
staticServer.on("clientError", (_err, socket) => {
|
|
socket.end("HTTP/1.1 400 Bad Request\r\n\r\n");
|
|
});
|
|
|
|
staticServer.listen(Number(ISOLATION_PORT), () => {
|
|
resetSuccessLog();
|
|
});
|
|
|
|
runRspack(rspackConfig);
|