Skip to content

Commit

Permalink
A moderately type safe config system
Browse files Browse the repository at this point in the history
  • Loading branch information
Milkshiift committed Aug 25, 2024
1 parent 92d7fa0 commit 4fcfafd
Show file tree
Hide file tree
Showing 13 changed files with 154 additions and 42 deletions.
9 changes: 7 additions & 2 deletions assets/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand All @@ -267,7 +268,11 @@
"inputType": "textfield"
},
"cloudToken": {
"defaultValue": ""
"defaultValue": "",
"outputType": "string"
},
"windowState:main": {
"defaultValue": [true, [0, 0], [835, 600]]
}
}
}
Expand Down
2 changes: 2 additions & 0 deletions build.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -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 = {
Expand Down
54 changes: 54 additions & 0 deletions genSettingsTypes.mjs
Original file line number Diff line number Diff line change
@@ -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}`);
}
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
48 changes: 34 additions & 14 deletions src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
Expand All @@ -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,
Expand All @@ -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<K extends ConfigKey>(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<K extends ConfigKey>(entry: K, value: Config[K]) {
try {
if (process.type !== "browser") {
await ipcRenderer.invoke("config:setConfig", entry, value);
Expand All @@ -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);
Expand All @@ -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);
Expand All @@ -85,10 +102,13 @@ export function getDefaults() {
defaults[setting] = categorySettings[setting].defaultValue;
}
}

return defaults;
}

export function getDefaultValue(entry: string) {
export function getDefaultValue<K extends ConfigKey>(entry: K): Config[K];
export function getDefaultValue(entry: string): unknown;
export function getDefaultValue(entry: string): unknown {
return getDefaults()[entry];
}

Expand Down
39 changes: 39 additions & 0 deletions src/configTypes.d.ts
Original file line number Diff line number Diff line change
@@ -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;
7 changes: 4 additions & 3 deletions src/modules/firewall.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<K extends ConfigKey>(toGet: K): Config[K] {
return getConfig("customFirewallRules") ? getConfig(toGet) : getDefaultValue(toGet);
}

export async function initializeFirewall() {
Expand Down Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion src/modules/messageEncryption.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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", () => {
Expand Down
13 changes: 4 additions & 9 deletions src/modules/windowStateManager.ts
Original file line number Diff line number Diff line change
@@ -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);
Expand All @@ -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);
});
}
4 changes: 2 additions & 2 deletions src/screenshare/preload.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down Expand Up @@ -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");
}
});
2 changes: 1 addition & 1 deletion src/settings/settingsRenderer.ts
Original file line number Diff line number Diff line change
@@ -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"));
Expand Down
8 changes: 2 additions & 6 deletions src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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");
}

Expand Down
4 changes: 2 additions & 2 deletions src/window.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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();
}
Expand Down Expand Up @@ -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" };
});
}

0 comments on commit 4fcfafd

Please sign in to comment.