From 0167b9d6a0f4262054ea5ced3728edb6e4297186 Mon Sep 17 00:00:00 2001 From: cody-ferguson <92231243+cody-ferguson@users.noreply.github.com> Date: Tue, 1 Jul 2025 13:30:55 -0400 Subject: [PATCH] Handle directories in mod protocol handler (#87) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Handle directories in mod protocol handler Previously we would return an error about a file not existing, which kinda makes sense, considering a directory isn't a file. Now with this commit, if the url resolve to a directory, it will return a json array with the relative paths of the file in that directory. * Make directory index work with ASAR archives Also fix directory index having higher priority than normal requests and simplify URL handling. --------- Co-authored-by: Даниїл Григор'єв --- electron/src/mods/protocol_handler.ts | 47 ++++++++++++++++++++++----- 1 file changed, 39 insertions(+), 8 deletions(-) diff --git a/electron/src/mods/protocol_handler.ts b/electron/src/mods/protocol_handler.ts index b6fc59db..ba586632 100644 --- a/electron/src/mods/protocol_handler.ts +++ b/electron/src/mods/protocol_handler.ts @@ -1,4 +1,5 @@ import { net, protocol } from "electron"; +import { lstat, readdir } from "node:fs/promises"; import path from "node:path"; import { pathToFileURL } from "node:url"; import { ModLoader } from "./loader.js"; @@ -31,20 +32,50 @@ export class ModProtocolHandler { } private async handler(request: GlobalRequest): Promise { + const fileUrl = this.getFileUrlForRequest(request); + if (fileUrl === undefined) { + return Response.error(); + } + try { - const fileUrl = this.getFileUrlForRequest(request); - if (fileUrl === undefined) { - return Response.error(); + return await net.fetch(fileUrl.toString()); + } catch (err) { + // Check if this is a directory request + const directoryIndex = await this.getDirectoryIndex(fileUrl); + if (directoryIndex !== null) { + return directoryIndex; } - return await net.fetch(fileUrl); - } catch (err) { console.error("Failed to fetch:", err); return Response.error(); } } - private getFileUrlForRequest(request: GlobalRequest): string | undefined { + private async getDirectoryIndex(fileUrl: URL): Promise { + if (!fileUrl.pathname.endsWith("/")) { + return null; + } + + // Remove the trailing slash + fileUrl.pathname = fileUrl.pathname.slice(0, -1); + + try { + const stats = await lstat(fileUrl); + if (!stats.isDirectory()) { + return null; + } + + const dir = await readdir(fileUrl, { withFileTypes: true }); + const result = dir.map(entry => entry.name + (entry.isDirectory() ? "/" : "")); + + return Response.json(result); + } catch (err) { + console.error("Failed to get directory index:", err); + return null; + } + } + + private getFileUrlForRequest(request: GlobalRequest): URL | undefined { // mod://mod-id/path/to/file const modUrl = new URL(request.url); const mod = this.modLoader.getModById(modUrl.hostname); @@ -58,10 +89,10 @@ export class ModProtocolHandler { // Check if the path escapes the bundle as per Electron example // NOTE: this means file names cannot start with .. const relative = path.relative(bundle, filePath); - if (relative === "" || relative.startsWith("..") || path.isAbsolute(relative)) { + if (relative.startsWith("..") || path.isAbsolute(relative)) { return undefined; } - return pathToFileURL(filePath).toString(); + return pathToFileURL(filePath); } }