317 lines
11 KiB
JavaScript
317 lines
11 KiB
JavaScript
const { app, BrowserWindow, Tray, Menu, nativeImage, ipcMain, Notification, dialog, shell } = require('electron');
|
|
const path = require('path');
|
|
const https = require('https');
|
|
const fs = require('fs');
|
|
const os = require('os');
|
|
|
|
// ─── CONFIG GITEA ─────────────────────────────────────────────────────────────
|
|
const GITEA_HOST = 'gitea.mickael-pol.fr'; // ton instance Gitea
|
|
const GITEA_USER = 'mickael'; // ton user Gitea
|
|
const GITEA_REPO = 'dd-timer'; // ton repo
|
|
const CURRENT_VERSION = app.getVersion(); // lu depuis package.json
|
|
|
|
let mainWindow;
|
|
let tray;
|
|
let isQuitting = false;
|
|
let updateInfo = null; // { version, downloadUrl } si mise à jour dispo
|
|
let downloading = false;
|
|
|
|
// ─── ICÔNE ───────────────────────────────────────────────────────────────────
|
|
function getTrayIcon() {
|
|
const fs = require('fs');
|
|
const iconPath = path.join(__dirname, 'icon.png');
|
|
if (fs.existsSync(iconPath)) return nativeImage.createFromPath(iconPath);
|
|
return nativeImage.createEmpty();
|
|
}
|
|
|
|
// ─── FENÊTRE ─────────────────────────────────────────────────────────────────
|
|
function createWindow() {
|
|
mainWindow = new BrowserWindow({
|
|
width: 1280, height: 900, minWidth: 960, minHeight: 650,
|
|
title: 'Minuteur Dragodinde - Dofus 3',
|
|
backgroundColor: '#0b0b14',
|
|
webPreferences: {
|
|
nodeIntegration: false,
|
|
contextIsolation: true,
|
|
backgroundThrottling: false,
|
|
preload: path.join(__dirname, 'preload.js'),
|
|
},
|
|
});
|
|
mainWindow.loadFile(path.join(__dirname, 'src', 'index.html'));
|
|
|
|
mainWindow.on('close', (e) => {
|
|
if (!isQuitting) {
|
|
e.preventDefault();
|
|
mainWindow.hide();
|
|
if (tray) tray.displayBalloon({
|
|
iconType: 'info',
|
|
title: 'Minuteur Dragodinde',
|
|
content: "Tourne en arriere-plan. Les alarmes sonneront normalement.",
|
|
});
|
|
}
|
|
});
|
|
|
|
// Envoyer les infos de version au renderer une fois chargé
|
|
mainWindow.webContents.on('did-finish-load', () => {
|
|
mainWindow.webContents.send('app-version', CURRENT_VERSION);
|
|
if (updateInfo) {
|
|
mainWindow.webContents.send('update-available', updateInfo);
|
|
}
|
|
});
|
|
}
|
|
|
|
// ─── TRAY ─────────────────────────────────────────────────────────────────────
|
|
function createTray() {
|
|
tray = new Tray(getTrayIcon());
|
|
tray.setToolTip(`Minuteur Dragodinde v${CURRENT_VERSION}`);
|
|
rebuildTrayMenu();
|
|
tray.on('double-click', () => { mainWindow.show(); mainWindow.focus(); });
|
|
}
|
|
|
|
function rebuildTrayMenu() {
|
|
if (!tray) return;
|
|
const items = [
|
|
{ label: `Minuteur Dragodinde v${CURRENT_VERSION}`, enabled: false },
|
|
{ type: 'separator' },
|
|
{ label: 'Ouvrir', click: () => { mainWindow.show(); mainWindow.focus(); } },
|
|
];
|
|
if (updateInfo) {
|
|
items.push({ type: 'separator' });
|
|
items.push({
|
|
label: `⬆ Mise a jour v${updateInfo.version} disponible !`,
|
|
click: () => startDownload(),
|
|
});
|
|
}
|
|
items.push({ type: 'separator' });
|
|
items.push({ label: 'Quitter', click: () => { isQuitting = true; app.quit(); } });
|
|
tray.setContextMenu(Menu.buildFromTemplate(items));
|
|
}
|
|
|
|
// ─── NOTIFICATIONS ────────────────────────────────────────────────────────────
|
|
function fireNotification(title, body) {
|
|
if (!Notification.isSupported()) return;
|
|
const n = new Notification({ title, body, timeoutType: 'never' });
|
|
n.on('click', () => { mainWindow.show(); mainWindow.focus(); });
|
|
n.show();
|
|
}
|
|
|
|
// ─── IPC ──────────────────────────────────────────────────────────────────────
|
|
ipcMain.on('trigger-alarm', (event, { enclosName }) => {
|
|
fireNotification('Dragodindes pretes !', enclosName + ' - Toutes les cibles ont ete atteintes !');
|
|
if (mainWindow && !mainWindow.isDestroyed()) {
|
|
mainWindow.webContents.send('play-alarm-sound');
|
|
}
|
|
});
|
|
|
|
ipcMain.on('show-notification', (event, { title, body }) => {
|
|
fireNotification(title, body);
|
|
});
|
|
|
|
ipcMain.on('focus-window', () => { mainWindow.show(); mainWindow.focus(); });
|
|
|
|
// Renderer demande à installer la mise à jour
|
|
ipcMain.on('install-update', () => startDownload());
|
|
|
|
// Renderer demande la version
|
|
ipcMain.handle('get-version', () => CURRENT_VERSION);
|
|
|
|
// ─── VÉRIFICATION DE MISE À JOUR ─────────────────────────────────────────────
|
|
function compareVersions(a, b) {
|
|
// Retourne > 0 si b > a (b est plus récent)
|
|
const pa = a.replace(/^v/, '').split('.').map(Number);
|
|
const pb = b.replace(/^v/, '').split('.').map(Number);
|
|
for (let i = 0; i < 3; i++) {
|
|
if ((pb[i] || 0) > (pa[i] || 0)) return 1;
|
|
if ((pb[i] || 0) < (pa[i] || 0)) return -1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
function checkForUpdates(silent = false) {
|
|
// API Gitea : GET /api/v1/repos/{user}/{repo}/releases?limit=1
|
|
// Retourne un tableau — le premier élément est la release la plus récente
|
|
const options = {
|
|
hostname: GITEA_HOST,
|
|
port: 443,
|
|
path: `/api/v1/repos/${GITEA_USER}/${GITEA_REPO}/releases?limit=1`,
|
|
method: 'GET',
|
|
headers: {
|
|
'User-Agent': `MinuteurDragodinde/${CURRENT_VERSION}`,
|
|
'Accept': 'application/json',
|
|
},
|
|
};
|
|
|
|
const req = https.request(options, (res) => {
|
|
let data = '';
|
|
res.on('data', chunk => { data += chunk; });
|
|
res.on('end', () => {
|
|
try {
|
|
const releases = JSON.parse(data);
|
|
|
|
// Gitea renvoie un tableau, on prend le premier (le plus récent)
|
|
if (!Array.isArray(releases) || releases.length === 0) return;
|
|
const release = releases[0];
|
|
|
|
const latestVersion = release.tag_name;
|
|
if (!latestVersion) return;
|
|
|
|
if (compareVersions(CURRENT_VERSION, latestVersion) > 0) {
|
|
// Chercher l'asset installeur (.exe contenant "Setup")
|
|
const asset = release.assets && release.assets.find(a =>
|
|
a.name.includes('Setup') && a.name.endsWith('.exe')
|
|
);
|
|
if (!asset) return;
|
|
|
|
updateInfo = {
|
|
version: latestVersion,
|
|
downloadUrl: asset.browser_download_url,
|
|
assetName: asset.name,
|
|
releaseNotes: release.body || '',
|
|
};
|
|
|
|
if (mainWindow && !mainWindow.isDestroyed()) {
|
|
mainWindow.webContents.send('update-available', updateInfo);
|
|
}
|
|
|
|
fireNotification(
|
|
`Mise a jour v${latestVersion} disponible !`,
|
|
'Cliquez pour mettre a jour Minuteur Dragodinde.'
|
|
);
|
|
|
|
rebuildTrayMenu();
|
|
|
|
} else if (!silent) {
|
|
if (mainWindow && !mainWindow.isDestroyed()) {
|
|
mainWindow.webContents.send('update-not-available');
|
|
}
|
|
}
|
|
} catch (e) {
|
|
console.error('Update check parse error:', e.message);
|
|
}
|
|
});
|
|
});
|
|
|
|
req.on('error', (e) => console.error('Update check error:', e.message));
|
|
req.end();
|
|
}
|
|
|
|
// ─── TÉLÉCHARGEMENT ET REMPLACEMENT ──────────────────────────────────────────
|
|
function startDownload() {
|
|
if (!updateInfo || downloading) return;
|
|
downloading = true;
|
|
|
|
if (mainWindow && !mainWindow.isDestroyed()) {
|
|
mainWindow.webContents.send('update-downloading', { version: updateInfo.version });
|
|
}
|
|
|
|
const tmpDir = os.tmpdir();
|
|
const tmpExe = path.join(tmpDir, updateInfo.assetName);
|
|
const file = fs.createWriteStream(tmpExe);
|
|
const currentExe = process.execPath;
|
|
|
|
// Suivre les redirections manuellement (GitHub assets redirigent)
|
|
function download(url, redirectCount = 0) {
|
|
if (redirectCount > 5) {
|
|
sendUpdateError('Trop de redirections.');
|
|
return;
|
|
}
|
|
const urlObj = new URL(url);
|
|
const mod = urlObj.protocol === 'https:' ? https : require('http');
|
|
const opts = {
|
|
hostname: urlObj.hostname,
|
|
path: urlObj.pathname + urlObj.search,
|
|
method: 'GET',
|
|
headers: { 'User-Agent': `MinuteurDragodinde/${CURRENT_VERSION}` },
|
|
};
|
|
|
|
mod.request(opts, (res) => {
|
|
if (res.statusCode === 302 || res.statusCode === 301) {
|
|
download(res.headers.location, redirectCount + 1);
|
|
return;
|
|
}
|
|
if (res.statusCode !== 200) {
|
|
sendUpdateError(`Erreur HTTP ${res.statusCode}`);
|
|
return;
|
|
}
|
|
|
|
const total = parseInt(res.headers['content-length'] || '0', 10);
|
|
let received = 0;
|
|
|
|
res.on('data', chunk => {
|
|
received += chunk.length;
|
|
file.write(chunk);
|
|
if (total > 0 && mainWindow && !mainWindow.isDestroyed()) {
|
|
const pct = Math.round((received / total) * 100);
|
|
mainWindow.webContents.send('update-progress', { percent: pct });
|
|
}
|
|
});
|
|
|
|
res.on('end', () => {
|
|
file.end();
|
|
file.on('finish', () => {
|
|
// Lancer le script de remplacement
|
|
launchUpdater(tmpExe, currentExe);
|
|
});
|
|
});
|
|
|
|
res.on('error', e => sendUpdateError(e.message));
|
|
}).on('error', e => sendUpdateError(e.message)).end();
|
|
}
|
|
|
|
download(updateInfo.downloadUrl);
|
|
}
|
|
|
|
function sendUpdateError(msg) {
|
|
downloading = false;
|
|
console.error('Update error:', msg);
|
|
if (mainWindow && !mainWindow.isDestroyed()) {
|
|
mainWindow.webContents.send('update-error', { message: msg });
|
|
}
|
|
}
|
|
|
|
function launchUpdater(newExe, currentExe) {
|
|
// Pour un installeur NSIS : on le lance directement avec /S pour silent install
|
|
// L'installeur gère lui-même le remplacement de l'ancienne version
|
|
if (mainWindow && !mainWindow.isDestroyed()) {
|
|
mainWindow.webContents.send('update-ready');
|
|
}
|
|
|
|
const { spawn } = require('child_process');
|
|
|
|
// Petit délai pour laisser le message s'afficher
|
|
setTimeout(() => {
|
|
// Lancer l'installeur NSIS en mode silencieux
|
|
// /S = silent, /D= permet de spécifier le dossier d'installation
|
|
spawn(newExe, ['/S'], {
|
|
detached: true,
|
|
stdio: 'ignore',
|
|
windowsHide: false, // L'installeur peut avoir besoin d'être visible pour UAC
|
|
}).unref();
|
|
|
|
isQuitting = true;
|
|
app.quit();
|
|
}, 1500);
|
|
}
|
|
|
|
// ─── CYCLE DE VIE ────────────────────────────────────────────────────────────
|
|
app.whenReady().then(() => {
|
|
createWindow();
|
|
createTray();
|
|
|
|
// Vérifier les mises à jour au démarrage (silencieux)
|
|
setTimeout(() => checkForUpdates(true), 3000);
|
|
|
|
// Revérifier toutes les heures
|
|
setInterval(() => checkForUpdates(true), 60 * 60 * 1000);
|
|
|
|
app.on('activate', () => {
|
|
if (BrowserWindow.getAllWindows().length === 0) createWindow();
|
|
});
|
|
});
|
|
|
|
app.on('window-all-closed', () => {
|
|
if (process.platform === 'darwin') app.quit();
|
|
});
|
|
|
|
app.on('before-quit', () => { isQuitting = true; });
|