Deno desktop stuff

This commit is contained in:
2026-09-26 13:25:34 -03:00
parent b68fe3a19f
commit 86d5c39eab
10 changed files with 457 additions and 170 deletions
+86
View File
@@ -0,0 +1,86 @@
/// <reference lib="deno.desktop" />
// the desktop app: the web client in a bundled chromium (deno desktop's cef backend), so people whose browser has no
// webgpu (firefox) can still play. it joins servers like the browser does, and runs its own game server for
// singleplayer, like minecraft java. build it with deno task desktop, see the desktop/ docs for how deno desktop works
import { fromFileUrl, join } from "@std/path";
import { type Host, serve_static, start_host } from "../server/host.ts";
// where the project was built from: build/ and server_mods/ are bundled into the app with --include
const ROOT = fromFileUrl(new URL("../", import.meta.url));
const BUILD_DIR = join(ROOT, "build");
// the server the address field starts with. "" leaves it empty for the player to fill in
const config: { server?: string } = JSON.parse(
await Deno.readTextFile(new URL("./config.json", import.meta.url)),
);
const default_server = Deno.env.get("BWORLD_SERVER") ?? config.server ?? "";
const win = new Deno.BrowserWindow({ title: "bworld", width: 1280, height: 720 });
// the singleplayer game server, started the first time singleplayer connects so players who only join other servers
// don't get a world made for them
let host: Host | undefined;
function singleplayer_host(): Host {
if (!host) {
const worlds = join(data_dir(), "worlds");
Deno.mkdirSync(worlds, { recursive: true });
host = start_host({
build_dir: BUILD_DIR,
server_mods_dir: join(ROOT, "server_mods"),
root: ROOT,
world_file: join(worlds, "world.json"),
on_fatal(message) {
console.error(message);
alert(`The singleplayer world stopped: ${message}`);
Deno.exit(1);
},
});
}
return host;
}
// the world gets saved before the app closes
win.addEventListener("close", async (event) => {
if (!host) {
return;
}
event.preventDefault();
try {
await host.shutdown();
} catch (e) {
console.error((e as Error).message);
}
Deno.exit(0);
});
// deno desktop binds this to a private local address and opens the window on it, the port given here is ignored
Deno.serve((req) => {
const url = new URL(req.url);
if (url.pathname === "/") {
// ?server= fills in the address field, without it the field would start with this app's own address.
// ?singleplayer shows the singleplayer button, which joins this app's own server
const start = new URL("/client/", url);
start.searchParams.set("server", default_server);
start.searchParams.set("singleplayer", "");
return Response.redirect(start, 302);
}
if (url.pathname === "/ws") {
return singleplayer_host().handle(req);
}
return serve_static(BUILD_DIR, req, url);
});
// where the app keeps its worlds, the usual place for app data on each os
function data_dir() {
const home = Deno.env.get("HOME") ?? Deno.env.get("USERPROFILE") ?? ".";
switch (Deno.build.os) {
case "windows":
return join(Deno.env.get("APPDATA") ?? join(home, "AppData", "Roaming"), "bworld");
case "darwin":
return join(home, "Library", "Application Support", "bworld");
default:
return join(Deno.env.get("XDG_DATA_HOME") ?? join(home, ".local", "share"), "bworld");
}
}