fix: implement robust multi-phase synchronization and server-side reconciliation
This commit is contained in:
@@ -214,6 +214,12 @@ const syncAccounts = async () => {
|
|||||||
for (const account of updatedAccounts) {
|
for (const account of updatedAccounts) {
|
||||||
try {
|
try {
|
||||||
const now = new Date();
|
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);
|
const lastCheck = account.lastBanCheck ? new Date(account.lastBanCheck) : new Date(0);
|
||||||
if ((now.getTime() - lastCheck.getTime()) / 3600000 > 6 || !account.personaName) {
|
if ((now.getTime() - lastCheck.getTime()) / 3600000 > 6 || !account.personaName) {
|
||||||
const profile = await (0, steam_web_1.fetchProfileData)(account.steamId, account.steamLoginSecure);
|
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;
|
return;
|
||||||
captured = true;
|
captured = true;
|
||||||
let serverSteamId = undefined;
|
let serverSteamId = undefined;
|
||||||
|
let isAdmin = false;
|
||||||
try {
|
try {
|
||||||
const payload = JSON.parse(Buffer.from(token.split('.')[1], 'base64').toString());
|
const payload = JSON.parse(Buffer.from(token.split('.')[1], 'base64').toString());
|
||||||
serverSteamId = payload.steamId;
|
serverSteamId = payload.steamId;
|
||||||
|
isAdmin = !!payload.isAdmin;
|
||||||
}
|
}
|
||||||
catch (e) { }
|
catch (e) { }
|
||||||
const current = store.get('serverConfig');
|
const current = store.get('serverConfig');
|
||||||
store.set('serverConfig', { ...current, token, serverSteamId, enabled: true });
|
store.set('serverConfig', { ...current, token, serverSteamId, isAdmin, enabled: true });
|
||||||
initBackend();
|
initBackend();
|
||||||
authWindow.close();
|
authWindow.close();
|
||||||
resolve(true);
|
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-community-accounts', async () => { initBackend(); return backend ? await backend.getCommunityAccounts() : []; });
|
||||||
electron_1.ipcMain.handle('get-server-users', async () => { initBackend(); return backend ? await backend.getServerUsers() : []; });
|
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('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-external', (event, url) => electron_1.shell.openExternal(url));
|
||||||
electron_1.ipcMain.handle('open-steam-app-login', async () => {
|
electron_1.ipcMain.handle('open-steam-app-login', async () => {
|
||||||
|
|||||||
@@ -21,6 +21,12 @@ electron_1.contextBridge.exposeInMainWorld('electronAPI', {
|
|||||||
syncNow: () => electron_1.ipcRenderer.invoke('sync-now'),
|
syncNow: () => electron_1.ipcRenderer.invoke('sync-now'),
|
||||||
getCommunityAccounts: () => electron_1.ipcRenderer.invoke('get-community-accounts'),
|
getCommunityAccounts: () => electron_1.ipcRenderer.invoke('get-community-accounts'),
|
||||||
getServerUsers: () => electron_1.ipcRenderer.invoke('get-server-users'),
|
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) => {
|
onAccountsUpdated: (callback) => {
|
||||||
const subscription = (_event, accounts) => callback(accounts);
|
const subscription = (_event, accounts) => callback(accounts);
|
||||||
electron_1.ipcRenderer.on('accounts-updated', subscription);
|
electron_1.ipcRenderer.on('accounts-updated', subscription);
|
||||||
|
|||||||
@@ -130,5 +130,59 @@ class BackendService {
|
|||||||
throw new Error(e.response?.data?.message || 'Failed to revoke all access');
|
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;
|
exports.BackendService = BackendService;
|
||||||
|
|||||||
@@ -242,6 +242,14 @@ const syncAccounts = async () => {
|
|||||||
for (const account of updatedAccounts) {
|
for (const account of updatedAccounts) {
|
||||||
try {
|
try {
|
||||||
const now = new Date();
|
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);
|
const lastCheck = account.lastBanCheck ? new Date(account.lastBanCheck) : new Date(0);
|
||||||
if ((now.getTime() - lastCheck.getTime()) / 3600000 > 6 || !account.personaName) {
|
if ((now.getTime() - lastCheck.getTime()) / 3600000 > 6 || !account.personaName) {
|
||||||
const profile = await fetchProfileData(account.steamId, account.steamLoginSecure);
|
const profile = await fetchProfileData(account.steamId, account.steamLoginSecure);
|
||||||
|
|||||||
Reference in New Issue
Block a user