diff --git a/apps/client/src/server.ts b/apps/client/src/server.ts index aa9da0e..371f226 100644 --- a/apps/client/src/server.ts +++ b/apps/client/src/server.ts @@ -83,20 +83,50 @@ const server = Bun.serve({ } }, "/manifest": async () => { - await updateManifest(dir); - return Response.json(MANIFEST, { - headers, - }); + try { + await updateManifest(dir); + return Response.json(MANIFEST, { + headers, + }); + } catch (error) { + console.error("Failed to load manifest:", error); + return Response.json( + { error: "Failed to load manifest", details: String(error) }, + { status: 500, headers }, + ); + } }, "/env": () => { - return Response.json(getGameEnv(), { headers }); + try { + const env = getGameEnv(); + return Response.json({ ...env, tlsEnabled: !!tls }, { headers }); + } catch (error) { + console.error("Failed to load environment:", error); + return Response.json( + { error: "Failed to load environment", details: String(error) }, + { status: 500, headers }, + ); + } }, - "/game/*": (req) => { - const path = new URL(req.url).pathname.replace("/game", ""); - const file = Bun.file(join(dir, path)); - return new Response(file, { - headers, - }); + "/game/*": async (req) => { + try { + const path = new URL(req.url).pathname.replace("/game", ""); + const file = Bun.file(join(dir, path)); + + if (!(await file.exists())) { + return Response.json({ error: "Game file not found", path }, { status: 404, headers }); + } + + return new Response(file, { + headers, + }); + } catch (error) { + console.error(`Error reading game file ${req.url}:`, error); + return Response.json( + { error: "Error reading game file", details: String(error) }, + { status: 500, headers }, + ); + } }, }, }); diff --git a/apps/website/src/cache/cache.ts b/apps/website/src/cache/cache.ts index 6612519..f5d0236 100644 --- a/apps/website/src/cache/cache.ts +++ b/apps/website/src/cache/cache.ts @@ -46,6 +46,22 @@ export class GameCache { private async _updateCacheFile(fileManifest: IManifestFile): Promise { const res = await fetch(`/game/${fileManifest.path.replace(/^\/+/, "")}`); + if (!res.ok) { + let errorMessage: string; + try { + const errorData = await res.json(); + + if (errorData.path) { + errorMessage = `${errorData.error} : ${errorData.path}`; + } else { + errorMessage = errorData.error || `HTTP Error ${res.status}`; + } + } catch { + errorMessage = `HTTP Error ${res.status} on file ${fileManifest.path}`; + } + throw new Error(errorMessage); + } + const file = await this.fs.getFile(fileManifest.path); const writable = await file.getWritable(); diff --git a/apps/website/src/index.ts b/apps/website/src/index.ts index 3c64905..991b999 100644 --- a/apps/website/src/index.ts +++ b/apps/website/src/index.ts @@ -11,6 +11,11 @@ const logger = new Logger("Loader"); const runLoad = async () => { logger.info("Starting loading game"); + if (!window.isSecureContext) { + throw new Error( + "Storage issue: The game must be hosted with SSL (HTTPS) to enable local storage and load properly.", + ); + } const manifest = await getManifest(); runWatcher(manifest.watch); @@ -22,6 +27,16 @@ const runLoad = async () => { runGame(mainModule, { files, env }); }; +window.addEventListener("error", (event) => { + setError(event.error || event.message); + logger.error(`Runtime error : ${event.message}`); +}); + +window.addEventListener("unhandledrejection", (event) => { + setError(event.reason); + logger.error(`Unhandled promise rejection : ${event.reason}`); +}); + runLoad() .then(() => { logger.info("Game loaded !"); diff --git a/apps/website/src/window.ts b/apps/website/src/window.ts index 6c0031e..b4a3996 100644 --- a/apps/website/src/window.ts +++ b/apps/website/src/window.ts @@ -37,11 +37,15 @@ export const setLoadingTotalFiles = (total: number) => { totalFiles = total; }; -export const setError = (error: string) => { +export const setError = (error: string | Error | unknown) => { const loaderErrorMessage = document.getElementById(IDS.loaderErrorMessage); if (!loaderErrorMessage) return; - loaderErrorMessage.innerText = error; + const errorMessage = error instanceof Error ? error.message : String(error); + loaderErrorMessage.innerText = errorMessage; + + setHiddenStatusOnId(IDS.container, true); + setHiddenStatusOnId(IDS.loader, false); setHiddenStatusOnId(IDS.loadingStatus, true); setHiddenStatusOnId(IDS.loaderError, false);