From ee44de182cd2aecf4e89b82991ec8b7799f9b80f Mon Sep 17 00:00:00 2001 From: Nils Pukropp Date: Sat, 21 Feb 2026 03:34:31 +0100 Subject: [PATCH] fix: implement robust multi-phase synchronization and server-side reconciliation --- frontend/dist-electron/main.js | 18 +++++++- frontend/dist-electron/preload.js | 6 +++ frontend/dist-electron/services/backend.js | 54 ++++++++++++++++++++++ frontend/electron/main.ts | 8 ++++ 4 files changed, 85 insertions(+), 1 deletion(-) diff --git a/frontend/dist-electron/main.js b/frontend/dist-electron/main.js index 3c52098..44ef93c 100644 --- a/frontend/dist-electron/main.js +++ b/frontend/dist-electron/main.js @@ -214,6 +214,12 @@ const syncAccounts = async () => { for (const account of updatedAccounts) { try { const now = new Date(); + // OPTIMIZATION: Ensure ALL authenticated accounts are shared with the server on every sync cycle + // this guarantees that even if a push failed previously, it will be reconciled now. + if (backend && !account._id.startsWith('shared_')) { + console.log(`[Sync] Reconciling account with server: ${account.personaName}`); + await backend.shareAccount(account); + } const lastCheck = account.lastBanCheck ? new Date(account.lastBanCheck) : new Date(0); if ((now.getTime() - lastCheck.getTime()) / 3600000 > 6 || !account.personaName) { const profile = await (0, steam_web_1.fetchProfileData)(account.steamId, account.steamLoginSecure); @@ -401,13 +407,15 @@ electron_1.ipcMain.handle('login-to-server', async () => { return; captured = true; let serverSteamId = undefined; + let isAdmin = false; try { const payload = JSON.parse(Buffer.from(token.split('.')[1], 'base64').toString()); serverSteamId = payload.steamId; + isAdmin = !!payload.isAdmin; } catch (e) { } const current = store.get('serverConfig'); - store.set('serverConfig', { ...current, token, serverSteamId, enabled: true }); + store.set('serverConfig', { ...current, token, serverSteamId, isAdmin, enabled: true }); initBackend(); authWindow.close(); resolve(true); @@ -516,6 +524,14 @@ electron_1.ipcMain.handle('revoke-all-account-access', async (event, steamId) => }); electron_1.ipcMain.handle('get-community-accounts', async () => { initBackend(); return backend ? await backend.getCommunityAccounts() : []; }); electron_1.ipcMain.handle('get-server-users', async () => { initBackend(); return backend ? await backend.getServerUsers() : []; }); +// --- Admin IPC --- +electron_1.ipcMain.handle('admin-get-stats', async () => { initBackend(); return backend ? await backend.getAdminStats() : null; }); +electron_1.ipcMain.handle('admin-get-users', async () => { initBackend(); return backend ? await backend.getAdminUsers() : []; }); +electron_1.ipcMain.handle('admin-delete-user', async (event, userId) => { initBackend(); if (backend) + await backend.deleteUser(userId); return true; }); +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('switch-account', async (event, loginName) => await handleSwitchAccount(loginName)); electron_1.ipcMain.handle('open-external', (event, url) => electron_1.shell.openExternal(url)); electron_1.ipcMain.handle('open-steam-app-login', async () => { diff --git a/frontend/dist-electron/preload.js b/frontend/dist-electron/preload.js index f6e21ce..854d74d 100644 --- a/frontend/dist-electron/preload.js +++ b/frontend/dist-electron/preload.js @@ -21,6 +21,12 @@ electron_1.contextBridge.exposeInMainWorld('electronAPI', { syncNow: () => electron_1.ipcRenderer.invoke('sync-now'), getCommunityAccounts: () => electron_1.ipcRenderer.invoke('get-community-accounts'), getServerUsers: () => electron_1.ipcRenderer.invoke('get-server-users'), + // Admin API + adminGetStats: () => electron_1.ipcRenderer.invoke('admin-get-stats'), + adminGetUsers: () => electron_1.ipcRenderer.invoke('admin-get-users'), + adminDeleteUser: (userId) => electron_1.ipcRenderer.invoke('admin-delete-user', userId), + adminGetAccounts: () => electron_1.ipcRenderer.invoke('admin-get-accounts'), + adminRemoveAccount: (steamId) => electron_1.ipcRenderer.invoke('admin-remove-account', steamId), onAccountsUpdated: (callback) => { const subscription = (_event, accounts) => callback(accounts); electron_1.ipcRenderer.on('accounts-updated', subscription); diff --git a/frontend/dist-electron/services/backend.js b/frontend/dist-electron/services/backend.js index d88977c..d7e23ba 100644 --- a/frontend/dist-electron/services/backend.js +++ b/frontend/dist-electron/services/backend.js @@ -130,5 +130,59 @@ class BackendService { throw new Error(e.response?.data?.message || 'Failed to revoke all access'); } } + // --- Admin API --- + async getAdminStats() { + if (!this.token) + return null; + try { + const response = await axios_1.default.get(`${this.url}/api/admin/stats`, { headers: this.headers }); + return response.data; + } + catch (e) { + return null; + } + } + async getAdminUsers() { + if (!this.token) + return []; + try { + const response = await axios_1.default.get(`${this.url}/api/admin/users`, { headers: this.headers }); + return response.data; + } + catch (e) { + return []; + } + } + async deleteUser(userId) { + if (!this.token) + return; + try { + await axios_1.default.delete(`${this.url}/api/admin/users/${userId}`, { headers: this.headers }); + } + catch (e) { + throw new Error(e.response?.data?.message || 'Failed to delete user'); + } + } + async getAdminAccounts() { + if (!this.token) + return []; + try { + const response = await axios_1.default.get(`${this.url}/api/admin/accounts`, { headers: this.headers }); + return response.data; + } + catch (e) { + return []; + } + } + async forceRemoveAccount(steamId) { + if (!this.token) + return; + try { + await axios_1.default.delete(`${this.url}/api/admin/accounts/${steamId}`, { headers: this.headers }); + } + catch (e) { + throw new Error(e.response?.data?.message || 'Failed to remove account'); + } + } } exports.BackendService = BackendService; diff --git a/frontend/electron/main.ts b/frontend/electron/main.ts index b9b197e..28f25f7 100644 --- a/frontend/electron/main.ts +++ b/frontend/electron/main.ts @@ -242,6 +242,14 @@ const syncAccounts = async () => { for (const account of updatedAccounts) { try { const now = new Date(); + + // OPTIMIZATION: Ensure ALL authenticated accounts are shared with the server on every sync cycle + // this guarantees that even if a push failed previously, it will be reconciled now. + if (backend && !account._id.startsWith('shared_')) { + console.log(`[Sync] Reconciling account with server: ${account.personaName}`); + await backend.shareAccount(account); + } + const lastCheck = account.lastBanCheck ? new Date(account.lastBanCheck) : new Date(0); if ((now.getTime() - lastCheck.getTime()) / 3600000 > 6 || !account.personaName) { const profile = await fetchProfileData(account.steamId, account.steamLoginSecure);