diff --git a/electron-builder.ts b/electron-builder.ts index fb12692..bd784de 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 69ac5b7..f2791e3 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 = 1000; // Cache for 1 second +const CONFIG_CACHE_TTL = 5000; // Cache for 5 seconds const defaults: Settings = { windowStyle: "default", channel: "stable", diff --git a/src/common/dom.ts b/src/common/dom.ts index c9840f6..4670db8 100644 --- a/src/common/dom.ts +++ b/src/common/dom.ts @@ -1,7 +1,12 @@ 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; @@ -9,6 +14,7 @@ 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; @@ -16,18 +22,21 @@ 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 9ff063b..7350856 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 = 1000; // Cache for 1 second +const WINDOW_STATE_CACHE_TTL = 5000; // Cache for 5 seconds export function getWindowStateLocation() { const userDataPath = app.getPath("userData"); diff --git a/src/discord/extensions/modloader.ts b/src/discord/extensions/modloader.ts index 1b2684b..98ee110 100644 --- a/src/discord/extensions/modloader.ts +++ b/src/discord/extensions/modloader.ts @@ -54,8 +54,7 @@ async function cacheCheck(mod: ValidMods) { } try { const latestRef = await getRef(modData[mod].repoData); - // biome-ignore lint/correctness/noConstantCondition: https://github.com/Legcord/Legcord/issues/763 - if (/*latestRef === modCache![mod]*/ false) { + if (latestRef === modCache![mod]) { console.log(`[Mod Loader]: ${mod} Cache hit!`); return; } else { diff --git a/src/discord/ipc.ts b/src/discord/ipc.ts index 7a9e5d7..de5c1c9 100644 --- a/src/discord/ipc.ts +++ b/src/discord/ipc.ts @@ -60,7 +60,11 @@ 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 770524a..406ea3b 100644 --- a/src/discord/preload/optimizer.ts +++ b/src/discord/preload/optimizer.ts @@ -1,18 +1,10 @@ type OptimizableFunction = (child: T) => T; const optimize = (orig: OptimizableFunction) => { - 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 function (this: Element, ...args: [Element]): 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 8d4b858..361406c 100644 --- a/src/discord/preload/patches.mts +++ b/src/discord/preload/patches.mts @@ -205,6 +205,7 @@ 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 ce673e6..4391a64 100644 --- a/src/discord/rpcProcess.ts +++ b/src/discord/rpcProcess.ts @@ -9,6 +9,9 @@ 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 a9d5abe..339e9f0 100644 --- a/src/discord/window.ts +++ b/src/discord/window.ts @@ -187,6 +187,14 @@ 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({}); }); @@ -268,9 +276,7 @@ function doAfterDefiningTheWindow(passedWindow: BrowserWindow): void { // Update window title with Legcord suffix if (!title.endsWith(legcordSuffix)) { e.preventDefault(); - // 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}`); + passedWindow.setTitle(title.replace("Discord |", "") + legcordSuffix); } }); injectThemesMain(passedWindow); @@ -303,18 +309,7 @@ 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 fc232b8..90e89fe 100644 --- a/src/shelter/settings/index.ts +++ b/src/shelter/settings/index.ts @@ -22,26 +22,30 @@ const { flux: { dispatcher, storesFlat }, } = shelter; -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 }), -]; +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 }), + ]; +} 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"], - body: () => store.i18n["settings-restartRequiredBody"], + header: () => store.i18n?.["settings-restartRequired"] ?? "Restart required", + body: () => store.i18n?.["settings-restartRequiredBody"] ?? "A restart is required to apply changes.", type: "danger", - confirmText: store.i18n["settings-restart"], - cancelText: store.i18n["settings-restartLater"], + confirmText: store.i18n?.["settings-restart"] ?? "Restart", + cancelText: store.i18n?.["settings-restartLater"] ?? "Later", }).then( () => window.legcord.restart(), () => console.log("restart skipped"), @@ -57,11 +61,11 @@ export function onLoad() { store.i18n = window.legcord.translations; log("Legcord Settings"); window.legcord.settings.setLang(storesFlat.LocaleStore.locale); - settingsPages; + settingsCleanups = registerSections(); dispatcher.subscribe("TRACK", restartRequired); } export function onUnload() { - settingsPages.forEach((e) => { + settingsCleanups.forEach((e) => { e(); }); dispatcher.unsubscribe("TRACK", restartRequired); diff --git a/src/shelter/settings/pages/SettingsPage.tsx b/src/shelter/settings/pages/SettingsPage.tsx index bfd5b64..92285ad 100644 --- a/src/shelter/settings/pages/SettingsPage.tsx +++ b/src/shelter/settings/pages/SettingsPage.tsx @@ -12,21 +12,24 @@ const { ui: { SwitchItem, Header, HeaderTags, Button, ButtonSizes }, } = shelter; -const settings = store.settings as Settings; -const noBundleUpdates = () => { +const noBundleUpdates = (settings: Settings) => { 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"]} + {store.i18n?.["settings-firstTimeCrash"] ?? "Setting things up..."}
-

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

+

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


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