From fc19f66acefb7a2c4c255b11802df4537013d2a3 Mon Sep 17 00:00:00 2001 From: Nils Pukropp Date: Sat, 21 Feb 2026 04:43:05 +0100 Subject: [PATCH 1/4] design: implement dynamic theme-based app icons for system tray and taskbar --- frontend/assets-build/icons/latte.svg | 4 +++ frontend/assets-build/icons/mocha.svg | 4 +++ frontend/assets-build/icons/nord.svg | 4 +++ frontend/assets-build/icons/steam.svg | 4 +++ frontend/assets-build/icons/tokyo.svg | 4 +++ frontend/electron/main.ts | 35 ++++++++++++++++++++++++++- frontend/electron/preload.ts | 1 + frontend/src/theme/ThemeContext.tsx | 10 ++++++++ 8 files changed, 65 insertions(+), 1 deletion(-) create mode 100644 frontend/assets-build/icons/latte.svg create mode 100644 frontend/assets-build/icons/mocha.svg create mode 100644 frontend/assets-build/icons/nord.svg create mode 100644 frontend/assets-build/icons/steam.svg create mode 100644 frontend/assets-build/icons/tokyo.svg diff --git a/frontend/assets-build/icons/latte.svg b/frontend/assets-build/icons/latte.svg new file mode 100644 index 0000000..927b015 --- /dev/null +++ b/frontend/assets-build/icons/latte.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/frontend/assets-build/icons/mocha.svg b/frontend/assets-build/icons/mocha.svg new file mode 100644 index 0000000..be50aab --- /dev/null +++ b/frontend/assets-build/icons/mocha.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/frontend/assets-build/icons/nord.svg b/frontend/assets-build/icons/nord.svg new file mode 100644 index 0000000..831f6fd --- /dev/null +++ b/frontend/assets-build/icons/nord.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/frontend/assets-build/icons/steam.svg b/frontend/assets-build/icons/steam.svg new file mode 100644 index 0000000..991fece --- /dev/null +++ b/frontend/assets-build/icons/steam.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/frontend/assets-build/icons/tokyo.svg b/frontend/assets-build/icons/tokyo.svg new file mode 100644 index 0000000..e7ed4c7 --- /dev/null +++ b/frontend/assets-build/icons/tokyo.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/frontend/electron/main.ts b/frontend/electron/main.ts index 8d3335c..09c1cb8 100644 --- a/frontend/electron/main.ts +++ b/frontend/electron/main.ts @@ -135,7 +135,14 @@ const createTray = () => { tray = new Tray(icon); tray.setToolTip('Ultimate Ban Tracker'); tray.on('click', () => { if (mainWindow) { mainWindow.show(); mainWindow.focus(); } }); - updateTrayMenu(); + + // Load initial themed icon + const config = store.get('serverConfig'); + if (config?.theme) { + setAppIcon(config.theme); + } else { + updateTrayMenu(); // Fallback to refresh menu + } } catch (e) { } }; @@ -162,6 +169,25 @@ const updateTrayMenu = () => { tray.setContextMenu(contextMenu); }; +const setAppIcon = (themeName: string = 'steam') => { + const assetsDir = path.join(__dirname, '..', 'assets-build', 'icons'); + const iconPath = path.join(assetsDir, `${themeName}.svg`); + + if (!fs.existsSync(iconPath)) return; + + const icon = nativeImage.createFromPath(iconPath); + + // Update Tray + if (tray) { + tray.setImage(icon.resize({ width: 16, height: 16 })); + } + + // Update Main Window + if (mainWindow) { + mainWindow.setIcon(icon); + } +}; + // --- Steam Logic --- const killSteam = async () => { return new Promise((resolve) => { @@ -571,6 +597,13 @@ ipcMain.handle('admin-delete-user', async (event, userId: string) => { initBacke ipcMain.handle('admin-get-accounts', async () => { initBackend(); return backend ? await backend.getAdminAccounts() : []; }); ipcMain.handle('admin-remove-account', async (event, steamId: string) => { initBackend(); if (backend) await backend.forceRemoveAccount(steamId); return true; }); +ipcMain.handle('force-sync', async () => { await syncAccounts(true); return true; }); + +ipcMain.handle('update-app-icon', (event, themeName: string) => { + setAppIcon(themeName); + return true; +}); + ipcMain.handle('switch-account', async (event, loginName: string) => { if (!loginName) return false; try { diff --git a/frontend/electron/preload.ts b/frontend/electron/preload.ts index 5da78f1..f18d9d4 100644 --- a/frontend/electron/preload.ts +++ b/frontend/electron/preload.ts @@ -10,6 +10,7 @@ contextBridge.exposeInMainWorld('electronAPI', { revokeAccountAccess: (steamId: string, targetSteamId: string) => ipcRenderer.invoke('revoke-account-access', steamId, targetSteamId), revokeAllAccountAccess: (steamId: string) => ipcRenderer.invoke('revoke-all-account-access', steamId), openExternal: (url: string) => ipcRenderer.invoke('open-external', url), + updateAppIcon: (theme: string) => ipcRenderer.invoke('update-app-icon', theme), openSteamAppLogin: () => ipcRenderer.invoke('open-steam-app-login'), openSteamLogin: (steamId: string) => ipcRenderer.invoke('open-steam-login', steamId), diff --git a/frontend/src/theme/ThemeContext.tsx b/frontend/src/theme/ThemeContext.tsx index 2f58364..4eb94a4 100644 --- a/frontend/src/theme/ThemeContext.tsx +++ b/frontend/src/theme/ThemeContext.tsx @@ -31,8 +31,18 @@ export const AppThemeProvider: React.FC<{ children: React.ReactNode }> = ({ chil if (api?.updateServerConfig) { await api.updateServerConfig({ theme }); } + if (api?.updateAppIcon) { + await api.updateAppIcon(theme); + } }; + useEffect(() => { + const api = (window as any).electronAPI; + if (api?.updateAppIcon && currentTheme) { + api.updateAppIcon(currentTheme); + } + }, [currentTheme]); + const theme = useMemo(() => getTheme(currentTheme), [currentTheme]); return ( From 776e05fb52944181551fb4727ac300191057d5b8 Mon Sep 17 00:00:00 2001 From: Nils Pukropp Date: Sat, 21 Feb 2026 04:44:10 +0100 Subject: [PATCH 2/4] fix: add safety checks for updateAppIcon IPC calls to prevent early startup race conditions --- frontend/dist-electron/main.js | 29 ++++++++++++++++++++++++++++- frontend/dist-electron/preload.js | 1 + frontend/src/theme/ThemeContext.tsx | 19 ++++++++++++++----- 3 files changed, 43 insertions(+), 6 deletions(-) diff --git a/frontend/dist-electron/main.js b/frontend/dist-electron/main.js index 4b529a1..2217aba 100644 --- a/frontend/dist-electron/main.js +++ b/frontend/dist-electron/main.js @@ -99,7 +99,14 @@ const createTray = () => { mainWindow.show(); mainWindow.focus(); } }); - updateTrayMenu(); + // Load initial themed icon + const config = store.get('serverConfig'); + if (config?.theme) { + setAppIcon(config.theme); + } + else { + updateTrayMenu(); // Fallback to refresh menu + } } catch (e) { } }; @@ -127,6 +134,21 @@ const updateTrayMenu = () => { ]); tray.setContextMenu(contextMenu); }; +const setAppIcon = (themeName = 'steam') => { + const assetsDir = path_1.default.join(__dirname, '..', 'assets-build', 'icons'); + const iconPath = path_1.default.join(assetsDir, `${themeName}.svg`); + if (!fs_1.default.existsSync(iconPath)) + return; + const icon = electron_1.nativeImage.createFromPath(iconPath); + // Update Tray + if (tray) { + tray.setImage(icon.resize({ width: 16, height: 16 })); + } + // Update Main Window + if (mainWindow) { + mainWindow.setIcon(icon); + } +}; // --- Steam Logic --- const killSteam = async () => { return new Promise((resolve) => { @@ -588,6 +610,11 @@ electron_1.ipcMain.handle('admin-delete-user', async (event, userId) => { initBa electron_1.ipcMain.handle('admin-get-accounts', async () => { initBackend(); return backend ? await backend.getAdminAccounts() : []; }); electron_1.ipcMain.handle('admin-remove-account', async (event, steamId) => { initBackend(); if (backend) await backend.forceRemoveAccount(steamId); return true; }); +electron_1.ipcMain.handle('force-sync', async () => { await syncAccounts(true); return true; }); +electron_1.ipcMain.handle('update-app-icon', (event, themeName) => { + setAppIcon(themeName); + return true; +}); electron_1.ipcMain.handle('switch-account', async (event, loginName) => { if (!loginName) return false; diff --git a/frontend/dist-electron/preload.js b/frontend/dist-electron/preload.js index 4c648d2..b376fc2 100644 --- a/frontend/dist-electron/preload.js +++ b/frontend/dist-electron/preload.js @@ -11,6 +11,7 @@ electron_1.contextBridge.exposeInMainWorld('electronAPI', { revokeAccountAccess: (steamId, targetSteamId) => electron_1.ipcRenderer.invoke('revoke-account-access', steamId, targetSteamId), revokeAllAccountAccess: (steamId) => electron_1.ipcRenderer.invoke('revoke-all-account-access', steamId), openExternal: (url) => electron_1.ipcRenderer.invoke('open-external', url), + updateAppIcon: (theme) => electron_1.ipcRenderer.invoke('update-app-icon', theme), openSteamAppLogin: () => electron_1.ipcRenderer.invoke('open-steam-app-login'), openSteamLogin: (steamId) => electron_1.ipcRenderer.invoke('open-steam-login', steamId), // Server Config & Auth diff --git a/frontend/src/theme/ThemeContext.tsx b/frontend/src/theme/ThemeContext.tsx index 4eb94a4..6ae749d 100644 --- a/frontend/src/theme/ThemeContext.tsx +++ b/frontend/src/theme/ThemeContext.tsx @@ -32,15 +32,24 @@ export const AppThemeProvider: React.FC<{ children: React.ReactNode }> = ({ chil await api.updateServerConfig({ theme }); } if (api?.updateAppIcon) { - await api.updateAppIcon(theme); + try { + await api.updateAppIcon(theme); + } catch (e) { } } }; useEffect(() => { - const api = (window as any).electronAPI; - if (api?.updateAppIcon && currentTheme) { - api.updateAppIcon(currentTheme); - } + const updateIcon = async () => { + const api = (window as any).electronAPI; + if (api?.updateAppIcon && currentTheme) { + try { + await api.updateAppIcon(currentTheme); + } catch (e) { + console.warn("[ThemeContext] updateAppIcon failed (likely not registered yet)"); + } + } + }; + updateIcon(); }, [currentTheme]); const theme = useMemo(() => getTheme(currentTheme), [currentTheme]); From 3f7c325604f737f5c940536c041978d3b9125604 Mon Sep 17 00:00:00 2001 From: Nils Pukropp Date: Sat, 21 Feb 2026 04:46:01 +0100 Subject: [PATCH 3/4] design: implement modern high-detail professional app icons with themed gradients and depth --- frontend/assets-build/icon.svg | 33 +++++++++++++++++++++++++-- frontend/assets-build/icons/latte.svg | 21 +++++++++++++++-- frontend/assets-build/icons/mocha.svg | 21 +++++++++++++++-- frontend/assets-build/icons/nord.svg | 21 +++++++++++++++-- frontend/assets-build/icons/steam.svg | 21 +++++++++++++++-- frontend/assets-build/icons/tokyo.svg | 21 +++++++++++++++-- 6 files changed, 126 insertions(+), 12 deletions(-) diff --git a/frontend/assets-build/icon.svg b/frontend/assets-build/icon.svg index 7bb25e5..bc0ba37 100644 --- a/frontend/assets-build/icon.svg +++ b/frontend/assets-build/icon.svg @@ -1,4 +1,33 @@ - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/frontend/assets-build/icons/latte.svg b/frontend/assets-build/icons/latte.svg index 927b015..0ff8cda 100644 --- a/frontend/assets-build/icons/latte.svg +++ b/frontend/assets-build/icons/latte.svg @@ -1,4 +1,21 @@ - - + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/frontend/assets-build/icons/mocha.svg b/frontend/assets-build/icons/mocha.svg index be50aab..c63ce66 100644 --- a/frontend/assets-build/icons/mocha.svg +++ b/frontend/assets-build/icons/mocha.svg @@ -1,4 +1,21 @@ - - + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/frontend/assets-build/icons/nord.svg b/frontend/assets-build/icons/nord.svg index 831f6fd..c2356c1 100644 --- a/frontend/assets-build/icons/nord.svg +++ b/frontend/assets-build/icons/nord.svg @@ -1,4 +1,21 @@ - - + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/frontend/assets-build/icons/steam.svg b/frontend/assets-build/icons/steam.svg index 991fece..024b2a7 100644 --- a/frontend/assets-build/icons/steam.svg +++ b/frontend/assets-build/icons/steam.svg @@ -1,4 +1,21 @@ - - + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/frontend/assets-build/icons/tokyo.svg b/frontend/assets-build/icons/tokyo.svg index e7ed4c7..8b2b023 100644 --- a/frontend/assets-build/icons/tokyo.svg +++ b/frontend/assets-build/icons/tokyo.svg @@ -1,4 +1,21 @@ - - + + + + + + + + + + + + + + + + + + + \ No newline at end of file From 4ad4e1c9deccd5f5f7569041af2e11880d5c3e72 Mon Sep 17 00:00:00 2001 From: Nils Pukropp Date: Sat, 21 Feb 2026 04:49:52 +0100 Subject: [PATCH 4/4] fix: implement failproof cross-platform session injection with forced VDF flags and registry synchronization --- frontend/electron/services/steam-client.ts | 86 +++++++--------------- 1 file changed, 26 insertions(+), 60 deletions(-) diff --git a/frontend/electron/services/steam-client.ts b/frontend/electron/services/steam-client.ts index 79487a7..fb401f5 100644 --- a/frontend/electron/services/steam-client.ts +++ b/frontend/electron/services/steam-client.ts @@ -55,20 +55,13 @@ class SteamClientService { return path.join(this.steamPath, 'config', 'config.vdf'); } - /** - * Safe Atomic Write: Writes to a temp file and renames it. - * This prevents file corruption if the app crashes during write. - */ private safeWriteVdf(filePath: string, data: any) { const tempPath = `${filePath}.tmp_${Date.now()}`; const dir = path.dirname(filePath); - try { if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true }); const vdfContent = stringify(data); fs.writeFileSync(tempPath, vdfContent, 'utf-8'); - - // Atomic rename fs.renameSync(tempPath, filePath); } catch (e: any) { console.error(`[SteamClient] Atomic write failed for ${filePath}: ${e.message}`); @@ -80,11 +73,9 @@ class SteamClientService { public startWatching(callback: (accounts: LocalSteamAccount[]) => void) { this.onAccountsChanged = callback; const loginUsersPath = this.getLoginUsersPath(); - if (loginUsersPath && fs.existsSync(loginUsersPath)) { this.readLocalAccounts(); chokidar.watch(loginUsersPath, { persistent: true, ignoreInitial: true }).on('change', () => { - console.log(`[SteamClient] loginusers.vdf changed, re-scanning...`); this.readLocalAccounts(); }); } @@ -93,64 +84,41 @@ class SteamClientService { private readLocalAccounts() { const filePath = this.getLoginUsersPath(); if (!filePath || !fs.existsSync(filePath)) return; - try { const content = fs.readFileSync(filePath, 'utf-8'); - if (!content.trim()) return; // Empty file - + if (!content.trim()) return; const data = parse(content) as any; if (!data || !data.users) return; - const accounts: LocalSteamAccount[] = []; for (const [steamId64, userData] of Object.entries(data.users)) { const user = userData as any; if (!user || !user.AccountName) continue; - accounts.push({ - steamId: steamId64, - accountName: user.AccountName, + steamId: steamId64, accountName: user.AccountName, personaName: user.PersonaName || user.AccountName, timestamp: parseInt(user.Timestamp) || 0 }); } - if (this.onAccountsChanged) this.onAccountsChanged(accounts); - } catch (error) { - console.error('[SteamClient] Error parsing loginusers.vdf:', error); - } + } catch (error) { } } public extractAccountConfig(accountName: string): any | null { const configPath = this.getConfigVdfPath(); if (!configPath || !fs.existsSync(configPath)) return null; - try { const content = fs.readFileSync(configPath, 'utf-8'); const data = parse(content) as any; const accounts = data?.InstallConfigStore?.Software?.Valve?.Steam?.Accounts; return (accounts && accounts[accountName]) ? accounts[accountName] : null; - } catch (e) { - console.error('[SteamClient] Failed to extract config.vdf data'); - return null; - } + } catch (e) { return null; } } public injectAccountConfig(accountName: string, accountData: any) { const configPath = this.getConfigVdfPath(); if (!configPath) return; - let data: any = { - InstallConfigStore: { - Software: { - Valve: { - Steam: { - Accounts: {} - } - } - } - } - }; - + let data: any = { InstallConfigStore: { Software: { Valve: { Steam: { Accounts: {} } } } } }; if (fs.existsSync(configPath)) { try { const content = fs.readFileSync(configPath, 'utf-8'); @@ -159,14 +127,24 @@ class SteamClientService { } catch (e) { } } - // Ensure safe nesting - if (!data.InstallConfigStore) data.InstallConfigStore = {}; - if (!data.InstallConfigStore.Software) data.InstallConfigStore.Software = {}; - if (!data.InstallConfigStore.Software.Valve) data.InstallConfigStore.Software.Valve = {}; - if (!data.InstallConfigStore.Software.Valve.Steam) data.InstallConfigStore.Software.Valve.Steam = {}; - if (!data.InstallConfigStore.Software.Valve.Steam.Accounts) data.InstallConfigStore.Software.Valve.Steam.Accounts = {}; + const ensurePath = (obj: any, keys: string[]) => { + let curr = obj; + for (const key of keys) { + if (!curr[key] || typeof curr[key] !== 'object') curr[key] = {}; + curr = curr[key]; + } + return curr; + }; - data.InstallConfigStore.Software.Valve.Steam.Accounts[accountName] = accountData; + const steamAccounts = ensurePath(data, ['InstallConfigStore', 'Software', 'Valve', 'Steam', 'Accounts']); + + // FAILPROOF: Force crucial flags that Steam uses to decide session validity + steamAccounts[accountName] = { + ...accountData, + RememberPassword: "1", + AllowAutoLogin: "1", + Timestamp: Math.floor(Date.now() / 1000).toString() + }; try { this.safeWriteVdf(configPath, data); @@ -226,6 +204,7 @@ class SteamClientService { } catch (e) { } } + // Injection of the actual authentication blob if (accountConfig && accountName) { this.injectAccountConfig(accountName, accountConfig); } @@ -239,13 +218,7 @@ class SteamClientService { for (const regPath of regLocations) { if (!fs.existsSync(path.dirname(regPath))) continue; - - let regData: any = { Registry: { HKCU: { Software: { Valve: { Steam: { - AutoLoginUser: "", - RememberPassword: "1", - AlreadyLoggedIn: "1" - } } } } } }; - + let regData: any = { Registry: { HKCU: { Software: { Valve: { Steam: { AutoLoginUser: "", RememberPassword: "1", AlreadyLoggedIn: "1" } } } } } }; if (fs.existsSync(regPath)) { try { const content = fs.readFileSync(regPath, 'utf-8'); @@ -254,13 +227,9 @@ class SteamClientService { } catch (e) { } } - // Deep merge helper const ensurePath = (obj: any, keys: string[]) => { let curr = obj; - for (const key of keys) { - if (!curr[key] || typeof curr[key] !== 'object') curr[key] = {}; - curr = curr[key]; - } + for (const key of keys) { if (!curr[key] || typeof curr[key] !== 'object') curr[key] = {}; curr = curr[key]; } return curr; }; @@ -269,10 +238,7 @@ class SteamClientService { steamKey.RememberPassword = "1"; steamKey.AlreadyLoggedIn = "1"; steamKey.WantsOfflineMode = "0"; - - try { - this.safeWriteVdf(regPath, regData); - } catch (e) { } + try { this.safeWriteVdf(regPath, regData); } catch (e) { } } }