diff --git a/electron-builder.ts b/electron-builder.ts index bd784de..fb12692 100644 --- a/electron-builder.ts +++ b/electron-builder.ts @@ -5,7 +5,7 @@ import { applyAppImageSandboxFix } from "./scripts/build/sandboxFix.mjs"; export const config: Configuration = { appId: "app.legcord.Legcord", productName: "Legcord", - artifactName: "Legcord-${version}-${os}-${arch}.${ext}", + artifactName: `Legcord-${version}-${os}-${arch}.${ext}`, beforePack: applyAppImageSandboxFix, protocols: [ { diff --git a/src/common/config.ts b/src/common/config.ts index f2791e3..69ac5b7 100644 --- a/src/common/config.ts +++ b/src/common/config.ts @@ -10,7 +10,7 @@ export let firstRun: boolean; // Performance optimization: Cache config to avoid reading file on every call let configCache: Settings | null = null; let configCacheTime = 0; -const CONFIG_CACHE_TTL = 5000; // Cache for 5 seconds +const CONFIG_CACHE_TTL = 1000; // Cache for 1 second const defaults: Settings = { windowStyle: "default", channel: "stable", diff --git a/src/common/dom.ts b/src/common/dom.ts index 4670db8..c9840f6 100644 --- a/src/common/dom.ts +++ b/src/common/dom.ts @@ -1,12 +1,7 @@ import type { BrowserWindow } from "electron"; -let scriptCounter = 0; - export function addStyle(styleUrl: string): void { - const id = `legcord-style-${styleUrl.replace(/[^a-zA-Z0-9]/g, "-")}`; - if (document.getElementById(id)) return; const style = document.createElement("link"); - style.id = id; style.rel = "stylesheet"; style.type = "text/css"; style.href = styleUrl; @@ -14,7 +9,6 @@ export function addStyle(styleUrl: string): void { } export function addTheme(id: string, styleString: string): void { - if (document.getElementById(id)) return; const style = document.createElement("style"); style.textContent = styleString; style.id = id; @@ -22,21 +16,18 @@ export function addTheme(id: string, styleString: string): void { } export function addScript(scriptString: string): void { - const id = `legcord-script-${++scriptCounter}`; - if (document.getElementById(id)) return; const script = document.createElement("script"); - script.id = id; script.appendChild(document.createTextNode(scriptString)); document.body.append(script); } export async function injectJS(inject: string): Promise { - const id = `legcord-inject-${inject.replace(/[^a-zA-Z0-9]/g, "-")}`; - if (document.getElementById(id)) return; const js = await (await fetch(`${inject}`)).text(); + const el = document.createElement("script"); - el.id = id; + el.appendChild(document.createTextNode(js)); + document.body.appendChild(el); } diff --git a/src/common/windowState.ts b/src/common/windowState.ts index 7350856..9ff063b 100644 --- a/src/common/windowState.ts +++ b/src/common/windowState.ts @@ -6,7 +6,7 @@ import type { WindowState } from "../@types/windowState.js"; // Performance optimization: Cache window state to avoid reading file on every call let windowStateCache: WindowState | null = null; let windowStateCacheTime = 0; -const WINDOW_STATE_CACHE_TTL = 5000; // Cache for 5 seconds +const WINDOW_STATE_CACHE_TTL = 1000; // Cache for 1 second export function getWindowStateLocation() { const userDataPath = app.getPath("userData"); diff --git a/src/discord/extensions/modloader.ts b/src/discord/extensions/modloader.ts index 98ee110..1b2684b 100644 --- a/src/discord/extensions/modloader.ts +++ b/src/discord/extensions/modloader.ts @@ -54,7 +54,8 @@ async function cacheCheck(mod: ValidMods) { } try { const latestRef = await getRef(modData[mod].repoData); - if (latestRef === modCache![mod]) { + // biome-ignore lint/correctness/noConstantCondition: https://github.com/Legcord/Legcord/issues/763 + if (/*latestRef === modCache![mod]*/ false) { console.log(`[Mod Loader]: ${mod} Cache hit!`); return; } else { diff --git a/src/discord/ipc.ts b/src/discord/ipc.ts index de5c1c9..7a9e5d7 100644 --- a/src/discord/ipc.ts +++ b/src/discord/ipc.ts @@ -60,11 +60,7 @@ function ifExistsRead(path: string): string | undefined { if (existsSync(path)) return readFileSync(path, "utf-8"); } -let ipcRegistered = false; - export function registerIpc(passedWindow: BrowserWindow): void { - if (ipcRegistered) return; - ipcRegistered = true; ipcMain.handle("getShelterBundle", () => { return { js: ifExistsRead(path.join(app.getPath("userData"), "shelter.js")), diff --git a/src/discord/preload/optimizer.ts b/src/discord/preload/optimizer.ts index 406ea3b..770524a 100644 --- a/src/discord/preload/optimizer.ts +++ b/src/discord/preload/optimizer.ts @@ -1,10 +1,18 @@ type OptimizableFunction = (child: T) => T; const optimize = (orig: OptimizableFunction) => { - return function (this: Element, ...args: [Element]): T { + return function (this: Element, ...args: [Element]): T | number { + if (typeof args[0]?.className === "string" && args[0].className.includes("activity")) { + // fix by xql.dev <@1356430365774053448> + setTimeout(() => orig.apply(this, args as unknown as [T]), 100); + return args[0] as unknown as T; + } return orig.apply(this, args as unknown as [T]); } as unknown as OptimizableFunction; }; +// We are taking in the function itself // eslint-disable-next-line @typescript-eslint/unbound-method Element.prototype.removeChild = optimize(Element.prototype.removeChild); + +// Thanks Ari - <@1249446413952225452> diff --git a/src/discord/preload/patches.mts b/src/discord/preload/patches.mts index 361406c..8d4b858 100644 --- a/src/discord/preload/patches.mts +++ b/src/discord/preload/patches.mts @@ -205,7 +205,6 @@ async function load() { el.id = "ac-ver"; el.textContent = `Legcord Version: ${version}`; info.after(el); - observer.disconnect(); }); observer.observe(document.body, { childList: true, subtree: true }); } diff --git a/src/discord/rpcProcess.ts b/src/discord/rpcProcess.ts index 4391a64..ce673e6 100644 --- a/src/discord/rpcProcess.ts +++ b/src/discord/rpcProcess.ts @@ -9,9 +9,6 @@ let rpcWorker: Worker; export let processList: GameList[] = []; export function startRPC(window: BrowserWindow) { - if (rpcWorker) { - rpcWorker.terminate(); - } const rpcPath = path.join(__dirname, "rpc.js"); rpcWorker = new Worker(rpcPath, { diff --git a/src/discord/window.ts b/src/discord/window.ts index 339e9f0..a9d5abe 100644 --- a/src/discord/window.ts +++ b/src/discord/window.ts @@ -187,14 +187,6 @@ function doAfterDefiningTheWindow(passedWindow: BrowserWindow): void { if (blockedPatterns.some((pattern) => pattern.test(details.url))) { return callback({ cancel: true }); } - if ( - details.url.includes("ws://127.0.0.1:") && - !details.url.includes("127.0.0.1:1211") && - !details.url.includes("127.0.0.1:1112") && - !details.url.includes("127.0.0.1:6888") - ) { - return callback({ cancel: true }); - } return callback({}); }); @@ -276,7 +268,9 @@ function doAfterDefiningTheWindow(passedWindow: BrowserWindow): void { // Update window title with Legcord suffix if (!title.endsWith(legcordSuffix)) { e.preventDefault(); - passedWindow.setTitle(title.replace("Discord |", "") + legcordSuffix); + // Security: Use JSON.stringify to prevent code injection via title + const safeTitle = JSON.stringify(title.replace("Discord |", "") + legcordSuffix); + void passedWindow.webContents.executeJavaScript(`document.title = ${safeTitle}`); } }); injectThemesMain(passedWindow); @@ -309,7 +303,18 @@ function doAfterDefiningTheWindow(passedWindow: BrowserWindow): void { }); setForceQuit(true); }); - + passedWindow.webContents.session.webRequest.onBeforeRequest((details, callback) => { + // Lune Dev exceptions, https://github.com/uwu/shelter/blob/8d4ca369bf01abf348df9d4e111d534800c7a38c/packages/shelter/src/devmode/index.tsx#L24 + if ( + details.url.includes("ws://127.0.0.1:") && + !details.url.includes("127.0.0.1:1211") && + !details.url.includes("127.0.0.1:1112") && + !details.url.includes("127.0.0.1:6888") + ) { + return callback({ cancel: true }); + } + return callback({}); + }); passedWindow.on("focus", () => { void passedWindow.webContents.executeJavaScript(`document.body.removeAttribute("unFocused");`); }); diff --git a/src/shelter/settings/index.ts b/src/shelter/settings/index.ts index 90e89fe..fc232b8 100644 --- a/src/shelter/settings/index.ts +++ b/src/shelter/settings/index.ts @@ -22,30 +22,26 @@ const { flux: { dispatcher, storesFlat }, } = shelter; -let settingsCleanups: (() => void)[] = []; - -function registerSections(): (() => void)[] { - return [ - registerSection("divider"), - registerSection("header", "Legcord"), - registerSection("section", "legcord-settings", "Settings", SettingsPage, { icon: SettingsSidebarIcon }), - registerSection("section", "legcord-themes", "Themes", ThemesPage, { icon: ThemesSidebarIcon }), - registerSection("section", "legcord-plugins", "Plugins", PluginsPage, { icon: PluginsSidebarIcon }), - registerSection("section", "legcord-keybinds", "Keybinds", KeybindsPage, { icon: KeybindsSidebarIcon }), - registerSection("section", "legcord-games", "Games", RegisteredGamesPage, { icon: GamesSidebarIcon }), - ]; -} +const settingsPages = [ + registerSection("divider"), + registerSection("header", "Legcord"), + registerSection("section", "legcord-settings", "Settings", SettingsPage, { icon: SettingsSidebarIcon }), + registerSection("section", "legcord-themes", "Themes", ThemesPage, { icon: ThemesSidebarIcon }), + registerSection("section", "legcord-plugins", "Plugins", PluginsPage, { icon: PluginsSidebarIcon }), + registerSection("section", "legcord-keybinds", "Keybinds", KeybindsPage, { icon: KeybindsSidebarIcon }), + registerSection("section", "legcord-games", "Games", RegisteredGamesPage, { icon: GamesSidebarIcon }), +]; function restartRequired(payload: { event: string; properties: { origin_pane: string } }) { if (payload.event === "settings_pane_viewed" && typeof payload.properties.origin_pane !== "undefined") { const pane = payload.properties.origin_pane; if ((pane === "legcord-settings" || pane === "legcord-games") && isRestartRequired) { openConfirmationModal({ - header: () => store.i18n?.["settings-restartRequired"] ?? "Restart required", - body: () => store.i18n?.["settings-restartRequiredBody"] ?? "A restart is required to apply changes.", + header: () => store.i18n["settings-restartRequired"], + body: () => store.i18n["settings-restartRequiredBody"], type: "danger", - confirmText: store.i18n?.["settings-restart"] ?? "Restart", - cancelText: store.i18n?.["settings-restartLater"] ?? "Later", + confirmText: store.i18n["settings-restart"], + cancelText: store.i18n["settings-restartLater"], }).then( () => window.legcord.restart(), () => console.log("restart skipped"), @@ -61,11 +57,11 @@ export function onLoad() { store.i18n = window.legcord.translations; log("Legcord Settings"); window.legcord.settings.setLang(storesFlat.LocaleStore.locale); - settingsCleanups = registerSections(); + settingsPages; dispatcher.subscribe("TRACK", restartRequired); } export function onUnload() { - settingsCleanups.forEach((e) => { + settingsPages.forEach((e) => { e(); }); dispatcher.unsubscribe("TRACK", restartRequired); diff --git a/src/shelter/settings/pages/SettingsPage.tsx b/src/shelter/settings/pages/SettingsPage.tsx index 92285ad..bfd5b64 100644 --- a/src/shelter/settings/pages/SettingsPage.tsx +++ b/src/shelter/settings/pages/SettingsPage.tsx @@ -12,24 +12,21 @@ const { ui: { SwitchItem, Header, HeaderTags, Button, ButtonSizes }, } = shelter; -const noBundleUpdates = (settings: Settings) => { +const settings = store.settings as Settings; +const noBundleUpdates = () => { const value = settings.noBundleUpdates; if (Array.isArray(value)) return value; return value ? ["shelter", "vencord", "equicord", "custom"] : []; }; export function SettingsPage() { - const settings = store.settings as Settings; if (!settings) { return ( <>
- {store.i18n?.["settings-firstTimeCrash"] ?? "Setting things up..."} + {store.i18n["settings-firstTimeCrash"]}
-

- {store.i18n?.["settings-firstTimeCrash-desc"] ?? - "Settings are not available on a first-time launch. Please restart."} -

+

{store.i18n["settings-firstTimeCrash-desc"]}


diff --git a/src/shelter/settings/settings.ts b/src/shelter/settings/settings.ts index 02fba15..87eec58 100644 --- a/src/shelter/settings/settings.ts +++ b/src/shelter/settings/settings.ts @@ -4,6 +4,8 @@ const { plugin: { store }, } = shelter; +const settings = store.settings as Settings; + export let isRestartRequired = false; export function setRestartRequired() { @@ -19,7 +21,7 @@ export function refreshThemes() { } export function setConfig(key: K, value: Settings[K], shouldRestart?: boolean) { - store.settings[key] = value; + settings[key] = value; console.log(key, ":", store.settings[key]); if (shouldRestart) { isRestartRequired = true; @@ -34,7 +36,7 @@ function removeMod(array: ValidMods[], filter: ValidMods) { export function toggleMod(mod: ValidMods, enabled: boolean) { isRestartRequired = true; - const currentMods = store.settings.mods; + const currentMods = settings.mods; if (enabled) { if (mod === "vencord") { currentMods.push("vencord");