diff --git a/assets/settings.json b/assets/settings.json index 3bbb466..7d9a7b3 100644 --- a/assets/settings.json +++ b/assets/settings.json @@ -252,7 +252,8 @@ "inputType": "checkbox" }, "screensharePreviousSettings": { - "defaultValue": ["720", "30", false, "motion"] + "defaultValue": ["720", "30", false, "motion"], + "outputType": "[number, number, boolean, string]" }, "cloudHost": { "name": "Cloud Host", @@ -267,7 +268,11 @@ "inputType": "textfield" }, "cloudToken": { - "defaultValue": "" + "defaultValue": "", + "outputType": "string" + }, + "windowState:main": { + "defaultValue": [true, [0, 0], [835, 600]] } } } diff --git a/build.mjs b/build.mjs index 59ede31..7ca2589 100644 --- a/build.mjs +++ b/build.mjs @@ -2,11 +2,13 @@ import { context } from "esbuild"; import fs from "fs"; import path from "path"; import * as readline from "readline"; +import { generateDTSFile } from "./genSettingsTypes.mjs"; const isDev = process.argv.some(arg => arg === "--dev" || arg === "-d"); await fs.promises.rm("ts-out", { recursive: true, force: true }); +generateDTSFile(); await fixArrpc(); const NodeCommonOpts = { diff --git a/genSettingsTypes.mjs b/genSettingsTypes.mjs new file mode 100644 index 0000000..1f18b39 --- /dev/null +++ b/genSettingsTypes.mjs @@ -0,0 +1,54 @@ +import fs from "fs"; +import path from "path"; + +const settingsPath = path.join(import.meta.dirname, "assets", "settings.json"); +const dtsPath = path.join(import.meta.dirname, "src", "configTypes.d.ts"); + +function generateType(settings) { + const lines = []; + lines.push("// This file is auto-generated. Any changes will be lost. See genSettingsTypes.mjs script"); + lines.push("export interface Config {"); + + for (const category in settings) { + const categorySettings = settings[category]; + + for (const settingKey in categorySettings) { + const setting = categorySettings[settingKey]; + let type; + if (setting["outputType"]) { + type = setting["outputType"]; + } else { + type = inferType(setting.inputType); + } + lines.push(` "${settingKey}": ${type};`); + } + } + + lines.push("}"); + lines.push("\nexport type ConfigKey = keyof Config;"); + + return lines.join("\n"); +} + +function inferType(inputType) { + switch (inputType) { + case "checkbox": + return "boolean"; + case "textfield": + case "dropdown": + case "file": + return "string"; + case "textarea": + case "dropdown-multiselect": + return "string[]"; + default: + return "unknown"; + } +} + +export function generateDTSFile() { + const settings = JSON.parse(fs.readFileSync(settingsPath, "utf-8")); + const dtsContent = generateType(settings); + fs.writeFileSync(dtsPath, dtsContent, "utf-8"); + console.log(`Generated settings.d.ts at ${dtsPath}`); +} \ No newline at end of file diff --git a/package.json b/package.json index 4a1bbf7..aa56041 100755 --- a/package.json +++ b/package.json @@ -5,8 +5,8 @@ "main": "ts-out/main.js", "scripts": { "build": "node build.mjs", - "start": "pnpm run build --dev && electron ./ts-out/main.js --dev", - "startWithLoginFix": "pnpm run fixLogin && pnpm run build --dev && electron ./ts-out/main.js --dev", + "start": "pnpm run build --dev && electron ./ts-out/main.js --dev --ozone-platform-hint=auto", + "startWithLoginFix": "pnpm run fixLogin && pnpm run build --dev && electron ./ts-out/main.js --dev --ozone-platform-hint=auto", "packageLinux": "pnpm run build && electron-builder --linux", "packageWindows": "pnpm run build && electron-builder --win", "packageMac": "pnpm run build && electron-builder --mac", diff --git a/src/config.ts b/src/config.ts index b04b3b2..66bec2d 100644 --- a/src/config.ts +++ b/src/config.ts @@ -2,8 +2,9 @@ import fs from "node:fs"; import path from "node:path"; import { dialog, ipcRenderer } from "electron"; import { getGoofCordFolderPath, tryWithFix } from "./utils"; +import type { Config, ConfigKey } from "./configTypes"; -export let cachedConfig: object = {}; +export let cachedConfig: Config; export let firstLaunch = false; export async function loadConfig() { @@ -12,6 +13,7 @@ export async function loadConfig() { // I don't know why but specifically in this scenario using fs.promises.readFile is whopping 180 ms compared to ~1 ms using fs.readFileSync // Related? https://github.com/nodejs/performance/issues/151 const rawData = fs.readFileSync(getConfigLocation(), "utf-8"); + // Read config *can* be of type other than "Config" if the user modifies it but that doesn't concern us :3 cachedConfig = JSON.parse(rawData); }, tryFixErrors, @@ -24,19 +26,33 @@ async function tryFixErrors() { await setup(); } -export function getConfig(toGet: string): any { +// Should be avoided. Use the type safe `getConfig` instead. +export function getConfigDynamic(toGet: string): unknown { if (process.type !== "browser") return ipcRenderer.sendSync("config:getConfig", toGet); const result = cachedConfig[toGet]; - if (result !== undefined) { - return result; - } + if (result !== undefined) return result; console.log("Missing config parameter:", toGet); - void setConfig(toGet, getDefaultValue(toGet)); + void setConfigDynamic(toGet, getDefaultValue(toGet)); return cachedConfig[toGet]; } -export async function setConfig(entry: string, value: unknown) { +export function getConfig(toGet: K): Config[K] { + if (process.type !== "browser") return ipcRenderer.sendSync("config:getConfig", toGet) as Config[K]; + + const result = cachedConfig[toGet]; + if (result !== undefined) return result; + console.log("Missing config parameter:", toGet); + void setConfig(toGet, getDefaultValue(toGet)); + return cachedConfig[toGet] as Config[K]; +} + +// Should be avoided. Use the type safe `setConfig` instead. +export async function setConfigDynamic(entry: string, value: unknown) { + await setConfig(entry as ConfigKey, value as Config[ConfigKey]); +} + +export async function setConfig(entry: K, value: Config[K]) { try { if (process.type !== "browser") { await ipcRenderer.invoke("config:setConfig", entry, value); @@ -50,13 +66,13 @@ export async function setConfig(entry: string, value: unknown) { } } -export async function setConfigBulk(object: object) { +export async function setConfigBulk(toSet: Config) { try { if (process.type !== "browser") { - return await ipcRenderer.invoke("config:setConfigBulk", object); + return await ipcRenderer.invoke("config:setConfigBulk", toSet); } - cachedConfig = object; - const toSave = JSON.stringify(object, undefined, 2); + cachedConfig = toSet; + const toSave = JSON.stringify(toSet, undefined, 2); await fs.promises.writeFile(getConfigLocation(), toSave, "utf-8"); } catch (e: any) { console.error("setConfigBulk function errored:", e); @@ -70,12 +86,13 @@ export async function setup() { await setConfigBulk(getDefaults()); } -const defaults = {}; -export function getDefaults() { +const defaults: Config = {} as Config; +export function getDefaults(): Config { // Caching if (Object.keys(defaults).length !== 0) { return defaults; } + const settingsPath = path.join(__dirname, "/assets/settings.json"); const settingsFile = fs.readFileSync(settingsPath, "utf-8"); const settings = JSON.parse(settingsFile); @@ -85,10 +102,13 @@ export function getDefaults() { defaults[setting] = categorySettings[setting].defaultValue; } } + return defaults; } -export function getDefaultValue(entry: string) { +export function getDefaultValue(entry: K): Config[K]; +export function getDefaultValue(entry: string): unknown; +export function getDefaultValue(entry: string): unknown { return getDefaults()[entry]; } diff --git a/src/configTypes.d.ts b/src/configTypes.d.ts new file mode 100644 index 0000000..7bbc0b8 --- /dev/null +++ b/src/configTypes.d.ts @@ -0,0 +1,39 @@ +// This file is auto-generated. Any changes will be lost. See genSettingsTypes.mjs script +export interface Config { + "customTitlebar": boolean; + "minimizeToTray": boolean; + "startMinimized": boolean; + "dynamicIcon": boolean; + "customIconPath": string; + "discordUrl": string; + "modNames": string[]; + "customJsBundle": string; + "customCssBundle": string; + "noBundleUpdates": boolean; + "firewall": boolean; + "customFirewallRules": boolean; + "blocklist": string[]; + "blockedStrings": string[]; + "allowedStrings": string[]; + "installDefaultShelterPlugins": boolean; + "invidiousEmbeds": boolean; + "messageEncryption": boolean; + "encryptionPasswords": string[]; + "encryptionCover": string; + "encryptionMark": string; + "arrpc": boolean; + "scriptLoading": boolean; + "launchWithOsBoot": boolean; + "spellcheck": boolean; + "popoutWindowAlwaysOnTop": boolean; + "updateNotification": boolean; + "autoscroll": boolean; + "transparency": boolean; + "screensharePreviousSettings": [number, number, boolean, string]; + "cloudHost": string; + "cloudEncryptionKey": string; + "cloudToken": string; + "windowState:main": unknown; +} + +export type ConfigKey = keyof Config; \ No newline at end of file diff --git a/src/modules/firewall.ts b/src/modules/firewall.ts index 7eb2998..2b9b6a2 100755 --- a/src/modules/firewall.ts +++ b/src/modules/firewall.ts @@ -2,9 +2,10 @@ import chalk from "chalk"; // This file contains everything that uses session.defaultSession.webRequest import { session } from "electron"; import { getConfig, getDefaultValue } from "../config"; +import type { Config, ConfigKey } from "../configTypes"; -function getConfigOrDefault(key: string): string[] { - return getConfig("customFirewallRules") ? getConfig(key) : getDefaultValue(key); +function getConfigOrDefault(toGet: K): Config[K] { + return getConfig("customFirewallRules") ? getConfig(toGet) : getDefaultValue(toGet); } export async function initializeFirewall() { @@ -55,7 +56,7 @@ export async function unstrictCSP() { //responseHeaders["access-control-allow-origin"] = ["*"]; if (resourceType === "mainFrame" || resourceType === "subFrame") { - responseHeaders["content-security-policy"] = undefined; + responseHeaders["content-security-policy"] = [""]; } else if (resourceType === "stylesheet") { // Fix hosts that don't properly set the css content type, such as // raw.githubusercontent.com diff --git a/src/modules/messageEncryption.ts b/src/modules/messageEncryption.ts index 5dd22ec..1216230 100755 --- a/src/modules/messageEncryption.ts +++ b/src/modules/messageEncryption.ts @@ -89,7 +89,7 @@ let currentIndex = 0; export function cycleThroughPasswords() { currentIndex = (currentIndex + 1) % encryptionPasswords.length; chosenPassword = encryptionPasswords[currentIndex]; - void mainWindow.webContents.executeJavaScript(`goofcord.titlebar.flashTitlebarWithText("#f9c23c", "${`Chosen password: ${chosenPassword.slice(0, 2)}...`}")`); + void mainWindow.webContents.executeJavaScript(`goofcord.titlebar.flashTitlebarWithText("#f9c23c", "Chosen password: ${chosenPassword.slice(0, 2)}...")`); } ipcMain.handle("messageEncryption:cycleThroughPasswords", () => { diff --git a/src/modules/windowStateManager.ts b/src/modules/windowStateManager.ts index d294d70..36d8050 100644 --- a/src/modules/windowStateManager.ts +++ b/src/modules/windowStateManager.ts @@ -1,16 +1,11 @@ import type { BrowserWindow } from "electron"; -import { getConfig, setConfig } from "../config"; +import { getConfigDynamic, setConfigDynamic } from "../config"; type NumberPair = [number, number]; type WindowState = [boolean, NumberPair, NumberPair]; -export function adjustWindow(window: BrowserWindow, windowName: string, defaults: WindowState) { - let previousWindowState = getConfig(`windowState:${windowName}`) as WindowState | undefined; - if (!previousWindowState) { - previousWindowState = defaults; - void setConfig(`windowState:${windowName}`, defaults); - } - +export function adjustWindow(window: BrowserWindow, configEntry: string) { + const previousWindowState = getConfigDynamic(configEntry) as WindowState; const [osMaximized, [x, y], [width, height]] = previousWindowState; window.setPosition(x, y); window.setSize(width, height); @@ -29,6 +24,6 @@ export function adjustWindow(window: BrowserWindow, windowName: string, defaults } const windowState: WindowState = [isMaximized, position, size]; - await setConfig(`windowState:${windowName}`, windowState); + await setConfigDynamic(configEntry, windowState); }); } diff --git a/src/screenshare/preload.ts b/src/screenshare/preload.ts index a42c39c..3f45695 100755 --- a/src/screenshare/preload.ts +++ b/src/screenshare/preload.ts @@ -41,7 +41,7 @@ async function selectSource(id: string | null, title: string | null) { const framerate = (document.getElementById("framerate-textbox") as HTMLInputElement).value; void ipcRenderer.invoke("flashTitlebar", "#5865F2"); - void setConfig("screensharePreviousSettings", [resolution, framerate, audio, contentHint]); + void setConfig("screensharePreviousSettings", [+resolution, +framerate, audio, contentHint]); void ipcRenderer.invoke("selectScreenshareSource", id, title, audio, contentHint, resolution, framerate); } catch (err) { @@ -113,6 +113,6 @@ document.addEventListener("keydown", (key) => { if (key.code === "Escape") { void ipcRenderer.invoke("selectScreenshareSource"); } else if (key.code === "Enter") { - selectSource("0", "Screen"); + void selectSource("0", "Screen"); } }); diff --git a/src/settings/settingsRenderer.ts b/src/settings/settingsRenderer.ts index 1e02902..085ca3a 100644 --- a/src/settings/settingsRenderer.ts +++ b/src/settings/settingsRenderer.ts @@ -1,7 +1,7 @@ import fs from "node:fs"; import path from "node:path"; import { ipcRenderer } from "electron"; -import { evaluateShowAfter, revertSetting } from "./preload"; +import { evaluateShowAfter } from "./preload"; const settingsPath = path.join(__dirname, "../", "/assets/settings.json"); const settings = JSON.parse(fs.readFileSync(settingsPath, "utf-8")); diff --git a/src/utils.ts b/src/utils.ts index 18d2bab..54da6b0 100755 --- a/src/utils.ts +++ b/src/utils.ts @@ -43,12 +43,8 @@ export function getDisplayVersion() { export function getCustomIcon() { const customIconPath = getConfig("customIconPath"); - if (typeof customIconPath === "string" && customIconPath !== "") { - return customIconPath; - } - if (process.platform === "win32") { - return path.join(__dirname, "/assets/gf_icon.ico"); - } + if (customIconPath !== "") return customIconPath; + if (process.platform === "win32") return path.join(__dirname, "/assets/gf_icon.ico"); return path.join(__dirname, "/assets/gf_icon.png"); } diff --git a/src/window.ts b/src/window.ts index 9795f2a..0d093f3 100755 --- a/src/window.ts +++ b/src/window.ts @@ -34,7 +34,7 @@ export async function createMainWindow() { }, }); - adjustWindow(mainWindow, "main", [true, [0, 0], [835, 600]]); + adjustWindow(mainWindow, "windowState:main"); if (getConfig("startMinimized")) mainWindow.hide(); await doAfterDefiningTheWindow(); } @@ -103,7 +103,7 @@ function setWindowOpenHandler() { }, }; } - if (url.startsWith("http") || url.startsWith("mailto:")) shell.openExternal(url); + if (url.startsWith("http") || url.startsWith("mailto:")) void shell.openExternal(url); return { action: "deny" }; }); }