fix(client): v1.3.0 — anti-loop updater, config backup, NSIS safe

Itens 0, 3 e 4 do plano de correção:

ITEM 0 — Preservar dados na atualização (não reconfigurar do zero)
- index.js: loadConfig/saveConfig grava config.backup.json a cada
  gravação válida; ao iniciar, restaura do backup se config.json
  sumir ou corromper — protege clientId e credenciais.
- client-monitor.js: nova persistConfig() grava config + backup juntos;
  aplicada nos 3 pontos que escrevem o config (clientId init, painel
  local, wizard CLI).
- package.json: deleteAppDataOnUninstall: false explícito.

ITEM 3 — Duplicatas na lista de apps do Windows
- Mantido perMachine: false consistente; GUID derivado do appId
  (não adicionado explícito para não criar 3ª entrada).
- deleteAppDataOnUninstall: false garante AppData intacto.

ITEM 4 — Loop de notificação de nova versão
- Trava anti-loop: conta tentativas por versão em update-state.json;
  após 3 falhas da mesma versão, para de auto-instalar e notifica
  balão com mensagem para reinstalar manualmente.
- reconcileUpdateState() no boot zera o contador quando a versão
  realmente avançou (instalação bem-sucedida).
- autoUpdater.on('error') agora exibe balão com erro real (throttle 60s).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
VPS 4 Builder
2026-05-31 05:05:36 +02:00
parent b23114ace2
commit 45b4c2215c
3 changed files with 171 additions and 16 deletions
+37 -6
View File
@@ -41,11 +41,42 @@ if (!fs.existsSync(appDataFolder)) {
// Carregar configuração dinamicamente para poder atualizá-la
const configPath = path.join(appDataFolder, 'config.json');
const configBackupPath = path.join(appDataFolder, 'config.backup.json');
// Lê e valida um JSON de config; retorna null se ausente/corrompido
function readConfigFileSafe(filePath) {
try {
if (!fs.existsSync(filePath)) return null;
const parsed = JSON.parse(fs.readFileSync(filePath, 'utf8'));
if (parsed && typeof parsed === 'object' && Object.keys(parsed).length > 0) {
return parsed;
}
return null;
} catch (e) {
return null;
}
}
// Grava config.json + cópia de segurança (preserva clientId/credenciais)
function persistConfig() {
fs.writeFileSync(configPath, JSON.stringify(config, null, 2));
try {
fs.writeFileSync(configBackupPath, JSON.stringify(config, null, 2));
} catch (e) {
console.error('Erro ao gravar backup de config:', e.message);
}
}
let config = {};
let configChanged = false;
try {
config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
} catch (e) {
// Tenta principal e, se faltar/corromper, restaura do backup antes de cair no default
config = readConfigFileSafe(configPath) || readConfigFileSafe(configBackupPath);
if (config) {
if (!fs.existsSync(configPath)) {
// Restaurado do backup: regrava o principal
configChanged = true;
}
} else {
config = {
serverUrl: "https://rx.scoreodonto.com",
monitorPath: "C:\\ProgramData\\RF\\Dental Sensor\\Images",
@@ -67,7 +98,7 @@ if (!config.clientId) {
if (configChanged) {
try {
fs.writeFileSync(configPath, JSON.stringify(config, null, 2));
persistConfig();
} catch (err) {
console.error('Erro ao inicializar clientId no config.json:', err.message);
}
@@ -858,7 +889,7 @@ function startAdminServer() {
config.adminPassword = newConfig.adminPassword;
try {
fs.writeFileSync(configPath, JSON.stringify(config, null, 2));
persistConfig();
addLog(`💾 Novas configurações persistidas em config.json`);
} catch (writeErr) {
res.writeHead(500, { 'Content-Type': 'application/json' });
@@ -1493,7 +1524,7 @@ function runSetupWizard() {
config.clientName = chosenName;
try {
fs.writeFileSync(configPath, JSON.stringify(config, null, 2));
persistConfig();
console.log('═══════════════════════════════════════════════════════');
console.log('✅ CONFIGURAÇÃO SALVA COM SUCESSO EM config.json!');
console.log(` Servidor Destino: ${config.serverUrl}`);