From 74231ca1906b309861e6ca18130d7daddb53bb79 Mon Sep 17 00:00:00 2001 From: imide Date: Thu, 16 Jul 2026 05:54:07 -0600 Subject: [PATCH 1/3] ci: upload generated-sources.json with releases (required for flatpak) (#1119) --- .github/workflows/flatpak-node.yml | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 .github/workflows/flatpak-node.yml diff --git a/.github/workflows/flatpak-node.yml b/.github/workflows/flatpak-node.yml new file mode 100644 index 0000000..082ee4a --- /dev/null +++ b/.github/workflows/flatpak-node.yml @@ -0,0 +1,28 @@ +name: Upload generated-sources.json to release for Flatpak building + +on: + release: + types: + - published + workflow_dispatch: + +permissions: + contents: write + +jobs: + upload: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Install flatpak-node-generator + run: pipx install git+https://github.com/flatpak/flatpak-builder-tools.git#subdirectory=node + + - name: Create generated-sources.json + run: /root/.local/bin/flatpak-node-generator pnpm pnpm-lock.yaml --node-sdk-extension org.freedesktop.Sdk.Extension.node26 --electron-node-headers + + - name: Upload generated-sources.json to release + run: | + gh release upload ${{ github.event.release.tag_name }} generated-sources.json + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} From 69b8d4202e882c922905852271780ecf7cde44b5 Mon Sep 17 00:00:00 2001 From: Nigel Thorne Date: Thu, 16 Jul 2026 21:56:55 +1000 Subject: [PATCH 2/3] docs: add clipboard fallback plugin example (#1123) --- .../clipboard-fallback-plugin/README.md | 82 +++++++ .../custom-bundle.js | 216 ++++++++++++++++++ .../clipboard-fallback-plugin/manifest.json | 9 + .../clipboard-fallback-plugin/renderer.js | 201 ++++++++++++++++ docs/plugin-system.md | 5 + electron-builder.ts | 2 + src/discord/window.ts | 38 ++- 7 files changed, 551 insertions(+), 2 deletions(-) create mode 100644 docs/examples/clipboard-fallback-plugin/README.md create mode 100644 docs/examples/clipboard-fallback-plugin/custom-bundle.js create mode 100644 docs/examples/clipboard-fallback-plugin/manifest.json create mode 100644 docs/examples/clipboard-fallback-plugin/renderer.js diff --git a/docs/examples/clipboard-fallback-plugin/README.md b/docs/examples/clipboard-fallback-plugin/README.md new file mode 100644 index 0000000..55d85b3 --- /dev/null +++ b/docs/examples/clipboard-fallback-plugin/README.md @@ -0,0 +1,82 @@ +# Legcord Clipboard Fallback Plugin + +Fixes Discord in-page copy actions in Legcord, including: + +- Copy User ID +- Copy Message ID +- Copy Message Link +- Other Discord menu actions that call `navigator.clipboard.writeText(...)` + +## Why this exists + +In affected Legcord/Electron environments, Discord's web UI calls: + +```js +navigator.clipboard.writeText(text) +``` + +but Chromium rejects it, commonly with errors like: + +```text +NotAllowedError: Failed to execute 'writeText' on 'Clipboard': Document is not focused. +``` + +or Legcord logs: + +```text +Unable to determine render window for element [object HTMLDocument] +``` + +This plugin patches `navigator.clipboard.writeText` in the Discord page and falls back to a selection-based `document.execCommand("copy")` copy path. + +## Known limitation + +Legcord's native **Copy Image** context-menu action does not go through `navigator.clipboard.writeText` or `navigator.clipboard.write` in the page. It is handled by Electron's main-process context menu (`webContents.copyImageAt(...)`), so a renderer/custom-bundle plugin cannot reliably fix image copying. That needs a Legcord main-process fix or a filesystem plugin with main/preload access on newer Legcord versions. + +## Install on Legcord versions with filesystem plugins + +1. Open the Legcord plugins folder: + + ```text + ~/Library/Application Support/legcord/plugins + ``` + +2. Create this folder: + + ```text + clipboard-fallback + ``` + +3. Copy these files into it: + + ```text + manifest.json + renderer.js + ``` + +4. Restart Legcord. +5. Enable **Clipboard Fallback** in Legcord's plugin settings. + +## Older Legcord workaround: custom bundle + +If your Legcord version does not have filesystem plugins yet, copy `custom-bundle.js` into: + +```text +~/Library/Application Support/legcord/custom.js +``` + +Do **not** use `renderer.js` as `custom.js`; `renderer.js` is the filesystem-plugin entry and expects Legcord's plugin loader to provide `module.exports`. + +and add `"custom"` to the `mods` array in: + +```text +~/Library/Application Support/legcord/storage/settings.json +``` + +Example: + +```json +"mods": ["equicord", "custom"] +``` + +Then restart Legcord. diff --git a/docs/examples/clipboard-fallback-plugin/custom-bundle.js b/docs/examples/clipboard-fallback-plugin/custom-bundle.js new file mode 100644 index 0000000..83cb93f --- /dev/null +++ b/docs/examples/clipboard-fallback-plugin/custom-bundle.js @@ -0,0 +1,216 @@ +(() => { + const module = { exports: {} }; + const api = { + logger: { + log: (...args) => console.log("[ClipboardFallback]", ...args), + warn: (...args) => console.warn("[ClipboardFallback]", ...args), + error: (...args) => console.error("[ClipboardFallback]", ...args), + }, + }; + + /** + * Legcord Clipboard Fallback + * + * Discord's web UI uses navigator.clipboard.writeText for actions such as + * "Copy User ID" and "Copy Message Link", and navigator.clipboard.write for + * richer clipboard payloads such as images. In some Legcord/Electron/macOS + * combinations Chromium rejects those calls because the document is not focused + * or the clipboard permission is not granted, leaving the clipboard unchanged. + * + * This renderer plugin replaces those APIs with selection-based copy fallbacks + * that run inside the original click gesture. + */ + module.exports.activate = (api) => { + const PATCH_KEY = Symbol.for("legcord.clipboardFallback.installed"); + + function install() { + try { + if (!navigator.clipboard) { + api.logger.warn("navigator.clipboard is unavailable"); + return; + } + + if (navigator.clipboard[PATCH_KEY]) return; + + const originalWriteText = navigator.clipboard.writeText?.bind(navigator.clipboard); + const originalWrite = navigator.clipboard.write?.bind(navigator.clipboard); + + async function blobToDataUrl(blob) { + if (typeof FileReader !== "undefined") { + return await new Promise((resolve, reject) => { + const reader = new FileReader(); + reader.onload = () => resolve(String(reader.result)); + reader.onerror = () => reject(reader.error ?? new Error("Failed to read clipboard image")); + reader.readAsDataURL(blob); + }); + } + + // Test/runtime fallback for environments with Blob but no FileReader. + const bytes = new Uint8Array(await blob.arrayBuffer()); + let binary = ""; + for (const byte of bytes) binary += String.fromCharCode(byte); + const base64 = typeof btoa === "function" ? btoa(binary) : Buffer.from(bytes).toString("base64"); + return `data:${blob.type || "application/octet-stream"};base64,${base64}`; + } + + function selectElementForCopy(element) { + const previousActiveElement = document.activeElement; + const selection = window.getSelection?.() ?? globalThis.getSelection?.(); + const range = document.createRange(); + + element.focus?.(); + range.selectNodeContents(element); + selection?.removeAllRanges(); + selection?.addRange(range); + + const copied = document.execCommand("copy"); + + selection?.removeAllRanges(); + if (previousActiveElement && typeof previousActiveElement.focus === "function") { + try { + previousActiveElement.focus(); + } catch {} + } + + if (!copied) throw new Error("document.execCommand('copy') returned false"); + } + + async function fallbackCopyText(text) { + const value = String(text); + const parent = document.body || document.documentElement; + if (!parent) throw new Error("No document body available for clipboard fallback"); + + const textarea = document.createElement("textarea"); + textarea.value = value; + textarea.setAttribute("readonly", ""); + textarea.setAttribute("aria-hidden", "true"); + textarea.style.position = "fixed"; + textarea.style.left = "-9999px"; + textarea.style.top = "0"; + textarea.style.width = "1px"; + textarea.style.height = "1px"; + textarea.style.opacity = "0"; + textarea.style.pointerEvents = "none"; + + parent.appendChild(textarea); + + const previousActiveElement = document.activeElement; + textarea.focus(); + textarea.select(); + textarea.setSelectionRange(0, value.length); + + const copied = document.execCommand("copy"); + textarea.remove(); + + if (previousActiveElement && typeof previousActiveElement.focus === "function") { + try { + previousActiveElement.focus(); + } catch {} + } + + if (!copied) throw new Error("document.execCommand('copy') returned false"); + } + + async function getClipboardItemType(item, type) { + if (!item?.types?.includes(type) || typeof item.getType !== "function") return null; + return await item.getType(type); + } + + async function fallbackCopyItems(items) { + const parent = document.body || document.documentElement; + if (!parent) throw new Error("No document body available for clipboard fallback"); + + const container = document.createElement("div"); + container.contentEditable = "true"; + container.setAttribute("aria-hidden", "true"); + container.style.position = "fixed"; + container.style.left = "-9999px"; + container.style.top = "0"; + container.style.width = "1px"; + container.style.height = "1px"; + container.style.overflow = "hidden"; + + for (const item of items) { + const htmlBlob = await getClipboardItemType(item, "text/html"); + if (htmlBlob) { + container.innerHTML += await htmlBlob.text(); + continue; + } + + const textBlob = await getClipboardItemType(item, "text/plain"); + if (textBlob) { + const span = document.createElement("span"); + span.textContent = await textBlob.text(); + container.appendChild(span); + continue; + } + + const imageType = item?.types?.find((type) => type.startsWith("image/")); + if (imageType && typeof item.getType === "function") { + const imageBlob = await item.getType(imageType); + const image = document.createElement("img"); + image.src = await blobToDataUrl(imageBlob); + image.alt = ""; + container.appendChild(image); + } + } + + if (!container.innerHTML && !container.textContent && !container.children?.length) { + throw new Error("No supported clipboard item types found"); + } + + parent.appendChild(container); + selectElementForCopy(container); + container.remove(); + } + + if (originalWriteText) { + Object.defineProperty(navigator.clipboard, "writeText", { + configurable: true, + value: async (text) => { + try { + await fallbackCopyText(text); + api.logger.log("copied text via fallback", text); + } catch (fallbackError) { + api.logger.warn("text fallback failed; trying original writeText", fallbackError); + return originalWriteText(text); + } + }, + }); + } + + if (originalWrite) { + Object.defineProperty(navigator.clipboard, "write", { + configurable: true, + value: async (items) => { + try { + await fallbackCopyItems(items); + api.logger.log("copied rich clipboard payload via fallback"); + } catch (fallbackError) { + api.logger.warn("rich clipboard fallback failed; trying original write", fallbackError); + return originalWrite(items); + } + }, + }); + } + + Object.defineProperty(navigator.clipboard, PATCH_KEY, { + configurable: false, + enumerable: false, + value: true, + }); + + api.logger.log("installed"); + } catch (error) { + api.logger.error("install failed", error); + } + } + + install(); + window.addEventListener("DOMContentLoaded", install, { once: true }); + }; + + if (typeof module.exports.activate === "function") { + module.exports.activate(api); + } +})(); diff --git a/docs/examples/clipboard-fallback-plugin/manifest.json b/docs/examples/clipboard-fallback-plugin/manifest.json new file mode 100644 index 0000000..7a509dd --- /dev/null +++ b/docs/examples/clipboard-fallback-plugin/manifest.json @@ -0,0 +1,9 @@ +{ + "id": "clipboard-fallback", + "name": "Clipboard Fallback", + "version": "1.1.0", + "description": "Fixes Discord in-page text copy actions in Legcord by falling back to document.execCommand('copy') when navigator.clipboard.writeText is blocked.", + "author": "Nigel Thornberry", + "compatibleVersions": ["*"], + "renderer": "renderer.js" +} diff --git a/docs/examples/clipboard-fallback-plugin/renderer.js b/docs/examples/clipboard-fallback-plugin/renderer.js new file mode 100644 index 0000000..33cdb01 --- /dev/null +++ b/docs/examples/clipboard-fallback-plugin/renderer.js @@ -0,0 +1,201 @@ +/** + * Legcord Clipboard Fallback + * + * Discord's web UI uses navigator.clipboard.writeText for actions such as + * "Copy User ID" and "Copy Message Link", and navigator.clipboard.write for + * richer clipboard payloads such as images. In some Legcord/Electron/macOS + * combinations Chromium rejects those calls because the document is not focused + * or the clipboard permission is not granted, leaving the clipboard unchanged. + * + * This renderer plugin replaces those APIs with selection-based copy fallbacks + * that run inside the original click gesture. + */ +module.exports.activate = (api) => { + const PATCH_KEY = Symbol.for("legcord.clipboardFallback.installed"); + + function install() { + try { + if (!navigator.clipboard) { + api.logger.warn("navigator.clipboard is unavailable"); + return; + } + + if (navigator.clipboard[PATCH_KEY]) return; + + const originalWriteText = navigator.clipboard.writeText?.bind(navigator.clipboard); + const originalWrite = navigator.clipboard.write?.bind(navigator.clipboard); + + async function blobToDataUrl(blob) { + if (typeof FileReader !== "undefined") { + return await new Promise((resolve, reject) => { + const reader = new FileReader(); + reader.onload = () => resolve(String(reader.result)); + reader.onerror = () => reject(reader.error ?? new Error("Failed to read clipboard image")); + reader.readAsDataURL(blob); + }); + } + + // Test/runtime fallback for environments with Blob but no FileReader. + const bytes = new Uint8Array(await blob.arrayBuffer()); + let binary = ""; + for (const byte of bytes) binary += String.fromCharCode(byte); + const base64 = typeof btoa === "function" ? btoa(binary) : Buffer.from(bytes).toString("base64"); + return `data:${blob.type || "application/octet-stream"};base64,${base64}`; + } + + function selectElementForCopy(element) { + const previousActiveElement = document.activeElement; + const selection = window.getSelection?.() ?? globalThis.getSelection?.(); + const range = document.createRange(); + + element.focus?.(); + range.selectNodeContents(element); + selection?.removeAllRanges(); + selection?.addRange(range); + + const copied = document.execCommand("copy"); + + selection?.removeAllRanges(); + if (previousActiveElement && typeof previousActiveElement.focus === "function") { + try { + previousActiveElement.focus(); + } catch {} + } + + if (!copied) throw new Error("document.execCommand('copy') returned false"); + } + + async function fallbackCopyText(text) { + const value = String(text); + const parent = document.body || document.documentElement; + if (!parent) throw new Error("No document body available for clipboard fallback"); + + const textarea = document.createElement("textarea"); + textarea.value = value; + textarea.setAttribute("readonly", ""); + textarea.setAttribute("aria-hidden", "true"); + textarea.style.position = "fixed"; + textarea.style.left = "-9999px"; + textarea.style.top = "0"; + textarea.style.width = "1px"; + textarea.style.height = "1px"; + textarea.style.opacity = "0"; + textarea.style.pointerEvents = "none"; + + parent.appendChild(textarea); + + const previousActiveElement = document.activeElement; + textarea.focus(); + textarea.select(); + textarea.setSelectionRange(0, value.length); + + const copied = document.execCommand("copy"); + textarea.remove(); + + if (previousActiveElement && typeof previousActiveElement.focus === "function") { + try { + previousActiveElement.focus(); + } catch {} + } + + if (!copied) throw new Error("document.execCommand('copy') returned false"); + } + + async function getClipboardItemType(item, type) { + if (!item?.types?.includes(type) || typeof item.getType !== "function") return null; + return await item.getType(type); + } + + async function fallbackCopyItems(items) { + const parent = document.body || document.documentElement; + if (!parent) throw new Error("No document body available for clipboard fallback"); + + const container = document.createElement("div"); + container.contentEditable = "true"; + container.setAttribute("aria-hidden", "true"); + container.style.position = "fixed"; + container.style.left = "-9999px"; + container.style.top = "0"; + container.style.width = "1px"; + container.style.height = "1px"; + container.style.overflow = "hidden"; + + for (const item of items) { + const htmlBlob = await getClipboardItemType(item, "text/html"); + if (htmlBlob) { + container.innerHTML += await htmlBlob.text(); + continue; + } + + const textBlob = await getClipboardItemType(item, "text/plain"); + if (textBlob) { + const span = document.createElement("span"); + span.textContent = await textBlob.text(); + container.appendChild(span); + continue; + } + + const imageType = item?.types?.find((type) => type.startsWith("image/")); + if (imageType && typeof item.getType === "function") { + const imageBlob = await item.getType(imageType); + const image = document.createElement("img"); + image.src = await blobToDataUrl(imageBlob); + image.alt = ""; + container.appendChild(image); + } + } + + if (!container.innerHTML && !container.textContent && !container.children?.length) { + throw new Error("No supported clipboard item types found"); + } + + parent.appendChild(container); + selectElementForCopy(container); + container.remove(); + } + + if (originalWriteText) { + Object.defineProperty(navigator.clipboard, "writeText", { + configurable: true, + value: async (text) => { + try { + await fallbackCopyText(text); + api.logger.log("copied text via fallback", text); + } catch (fallbackError) { + api.logger.warn("text fallback failed; trying original writeText", fallbackError); + return originalWriteText(text); + } + }, + }); + } + + if (originalWrite) { + Object.defineProperty(navigator.clipboard, "write", { + configurable: true, + value: async (items) => { + try { + await fallbackCopyItems(items); + api.logger.log("copied rich clipboard payload via fallback"); + } catch (fallbackError) { + api.logger.warn("rich clipboard fallback failed; trying original write", fallbackError); + return originalWrite(items); + } + }, + }); + } + + Object.defineProperty(navigator.clipboard, PATCH_KEY, { + configurable: false, + enumerable: false, + value: true, + }); + + api.logger.log("installed"); + } catch (error) { + api.logger.error("install failed", error); + } + } + + install(); + window.addEventListener("DOMContentLoaded", install, { once: true }); +}; diff --git a/docs/plugin-system.md b/docs/plugin-system.md index bfe227c..bc47ed1 100644 --- a/docs/plugin-system.md +++ b/docs/plugin-system.md @@ -115,6 +115,11 @@ A complete example is available at: - `docs/examples/hello-plugin/preload.js` - `docs/examples/hello-plugin/renderer.js` +A practical renderer-only workaround plugin is also available at: + +- `docs/examples/clipboard-fallback-plugin/manifest.json` +- `docs/examples/clipboard-fallback-plugin/renderer.js` + To test it: 1. Copy `docs/examples/hello-plugin` into your runtime plugins directory: diff --git a/electron-builder.ts b/electron-builder.ts index 9a6ec11..06b45c9 100644 --- a/electron-builder.ts +++ b/electron-builder.ts @@ -5,6 +5,8 @@ import { applyAppImageSandboxFix } from "./scripts/build/sandboxFix.mjs"; export const config: Configuration = { appId: "app.legcord.Legcord", productName: "Legcord", + // Biome treats electron-builder macro placeholders as template syntax. + // biome-ignore lint/suspicious/noTemplateCurlyInString: electron-builder expands these placeholders. artifactName: "Legcord-${version}-${os}-${arch}.${ext}", beforePack: applyAppImageSandboxFix, protocols: [ diff --git a/src/discord/window.ts b/src/discord/window.ts index c969714..e95a9b9 100644 --- a/src/discord/window.ts +++ b/src/discord/window.ts @@ -5,9 +5,11 @@ import { app, BrowserWindow, type BrowserWindowConstructorOptions, + clipboard, dialog, type MessageBoxOptions, nativeImage, + net, screen, shell, } from "electron"; @@ -75,10 +77,42 @@ function saveWindowState(win: BrowserWindow): void { } } +async function copyImageFromContext( + parameters: { srcURL: string; x: number; y: number }, + win?: BrowserWindow, +): Promise { + if (parameters.srcURL) { + try { + const response = await net.fetch(parameters.srcURL); + if (!response.ok) throw new Error(`HTTP ${response.status} ${response.statusText}`); + + const image = nativeImage.createFromBuffer(Buffer.from(await response.arrayBuffer())); + if (!image.isEmpty()) { + clipboard.writeImage(image); + return; + } + } catch (error) { + console.warn("[ContextMenu] Failed to copy image from URL, falling back to copyImageAt:", error); + } + } + + win?.webContents.copyImageAt(parameters.x, parameters.y); +} + contextMenu({ showSaveImageAs: true, + showCopyImage: false, showCopyImageAddress: true, showSearchWithGoogle: false, + append: (_defaultActions, parameters, win) => [ + { + label: "Copy Image", + visible: parameters.mediaType === "image", + click: () => { + void copyImageFromContext(parameters, win as BrowserWindow | undefined); + }, + }, + ], prepend: (_defaultActions, parameters) => [ { label: getLang("contextMenu-searchGoogle"), @@ -423,7 +457,7 @@ function doAfterDefiningTheWindow(passedWindow: BrowserWindow): void { lastPolledBounds = { x, y, width, height }; saveWindowState(passedWindow); } - } catch (e) { + } catch (_e) { // ignore transient errors } }, 1000); @@ -520,7 +554,7 @@ export function createWindow() { mainWindow.setPosition(storedBounds.x, storedBounds.y); mainWindow.setSize(storedBounds.width, storedBounds.height); } - + mainWindows.push(mainWindow); doAfterDefiningTheWindow(mainWindow); } From dc7be47cbc6e47e5b3b5468f8490b2a977f2a4e3 Mon Sep 17 00:00:00 2001 From: youtsuho <110821381+youtsuhodev@users.noreply.github.com> Date: Thu, 16 Jul 2026 13:57:52 +0200 Subject: [PATCH 3/3] fix: use legcord:// protocol for local HTML files (#1120) --- package.json | 6 +++--- src/cssEditor/main.ts | 2 +- src/protocol.ts | 30 ++++++++++++++++++++++++++++++ src/setup/main.ts | 3 +-- src/splash/main.ts | 2 +- 5 files changed, 36 insertions(+), 7 deletions(-) diff --git a/package.json b/package.json index 1a5c15a..530b752 100644 --- a/package.json +++ b/package.json @@ -7,9 +7,9 @@ "node": ">=26" }, "scripts": { - "build:dev": "rollup -c --environment BUILD:dev && node scripts/copyVenmic.ts", + "build:dev": "rollup -c --environment BUILD:dev && node --experimental-strip-types scripts/copyVenmic.ts", "build:plugins": "lune ci --repoSubDir src/shelter --to ts-out/plugins", - "build": "pnpm build:plugins && rolldown -c rolldown.config.ts && node scripts/copyVenmic.ts", + "build": "pnpm build:plugins && rolldown -c rolldown.config.ts && node --experimental-strip-types scripts/copyVenmic.ts", "start": "pnpm run build && electron --trace-warnings --ozone-platform-hint=auto ./ts-out/main.js", "startThemeManager": "pnpm run build:dev && electron ./ts-out/main.js themes", "package": "pnpm run build && electron-builder", @@ -18,7 +18,7 @@ "lint:fix": "biome check --write", "postinstall": "electron-builder install-app-deps", "CIbuild": "pnpm run build && electron-builder --linux zip && electron-builder --windows zip && electron-builder --macos zip", - "updateMeta": "node scripts/utils/updateMeta.ts" + "updateMeta": "node --experimental-strip-types scripts/utils/updateMeta.ts" }, "repository": { "type": "git", diff --git a/src/cssEditor/main.ts b/src/cssEditor/main.ts index c6609cd..9543d97 100644 --- a/src/cssEditor/main.ts +++ b/src/cssEditor/main.ts @@ -14,7 +14,7 @@ export function openCssEditor(file: string) { preload: path.join(import.meta.dirname, "cssEditor", "preload.mjs"), }, }); - cssWindow.loadURL(`file://${import.meta.dirname}/html/editor.html`); + cssWindow.loadURL("legcord://html/editor.html"); ipcMain.on("editor-setCSS", (_event, css: string) => { fs.writeFileSync(file, css); diff --git a/src/protocol.ts b/src/protocol.ts index 10b92b6..01c2507 100644 --- a/src/protocol.ts +++ b/src/protocol.ts @@ -29,6 +29,26 @@ void app.whenReady().then(() => { }); } return net.fetch(Url.pathToFileURL(filePath).toString()); + } else if (req.url.startsWith("legcord://html/")) { + const file = req.url.replace("legcord://html/", ""); + const filePath = path.join(import.meta.dirname, "html", `${file}`); + if (filePath.includes("..")) { + return new Response("bad", { + status: 400, + headers: { "content-type": "text/html" }, + }); + } + return net.fetch(Url.pathToFileURL(filePath).toString()); + } else if (req.url.startsWith("legcord://js/")) { + const file = req.url.replace("legcord://js/", ""); + const filePath = path.join(import.meta.dirname, "js", `${file}`); + if (filePath.includes("..")) { + return new Response("bad", { + status: 400, + headers: { "content-type": "text/html" }, + }); + } + return net.fetch(Url.pathToFileURL(filePath).toString()); } else if (req.url.startsWith("legcord://assets/")) { const file = req.url.replace("legcord://assets/", ""); const filePath = path.join(import.meta.dirname, "assets", "app", `${file}`); @@ -39,6 +59,16 @@ void app.whenReady().then(() => { }); } return net.fetch(Url.pathToFileURL(filePath).toString()); + } else if (req.url.startsWith("legcord://css/")) { + const file = req.url.replace("legcord://css/", ""); + const filePath = path.join(import.meta.dirname, "css", `${file}`); + if (filePath.includes("..")) { + return new Response("bad", { + status: 400, + headers: { "content-type": "text/html" }, + }); + } + return net.fetch(Url.pathToFileURL(filePath).toString()); } else if (req.url.startsWith("legcord://local/")) { const file = req.url.replace("legcord://local/", ""); const userDataPath = path.join(app.getPath("userData"), "userAssets"); diff --git a/src/setup/main.ts b/src/setup/main.ts index eadede6..697c8c1 100644 --- a/src/setup/main.ts +++ b/src/setup/main.ts @@ -22,7 +22,6 @@ export async function createSetupWindow(): Promise { maximizable: false, autoHideMenuBar: true, webPreferences: { - sandbox: true, spellcheck: false, preload: path.join(import.meta.dirname, "setup", "preload.mjs"), }, @@ -70,6 +69,6 @@ export async function createSetupWindow(): Promise { // workaround electron trying to relaunch from squashfs handleRestart(); }); - void setupWindow.loadFile(path.join(import.meta.dirname, "/html/setup.html")); + void setupWindow.loadURL("legcord://html/setup.html"); }); } diff --git a/src/splash/main.ts b/src/splash/main.ts index c4d5433..4ace7fa 100644 --- a/src/splash/main.ts +++ b/src/splash/main.ts @@ -31,5 +31,5 @@ export async function createSplashWindow(): Promise { ipcMain.on("splash-clientmod", (event) => { event.returnValue = getConfig("mods"); }); - await splashWindow.loadFile(path.join(import.meta.dirname, "html", "splash.html")); + await splashWindow.loadURL("legcord://html/splash.html"); }