const { app, BrowserWindow, session, shell, desktopCapturer, autoUpdater, dialog, ipcMain, Notification } = require('electron'); const path = require('path'); const fs = require('fs'); const os = require('os'); const { setupAdBlocker } = require('./ad-blocker'); // Handle Squirrel events (Windows installer) try { if (require('electron-squirrel-startup')) app.quit(); } catch { // electron-squirrel-startup not installed, skip } const HUB_URL = process.env.GAMING_HUB_URL || 'https://hub.daddelolymp.de'; const APP_VERSION = app.getVersion(); // Sync IPC: preload reads app version from package.json ipcMain.on('get-app-version', (event) => { event.returnValue = APP_VERSION; }); function setupAutoUpdater() { if (process.platform !== 'win32') return; // Squirrel.Windows appends /RELEASES to the feed URL, // so point to /downloads where RELEASES + nupkg files live const updateURL = `${HUB_URL}/downloads`; try { autoUpdater.setFeedURL({ url: updateURL }); } catch (e) { console.error('[AutoUpdater] setFeedURL failed:', e.message); return; } autoUpdater.on('update-available', () => { console.log('[AutoUpdater] Update available, downloading...'); if (mainWindow) mainWindow.webContents.send('update-available'); }); autoUpdater.on('update-downloaded', (_event, releaseNotes, releaseName) => { console.log('[AutoUpdater] Update downloaded:', releaseName || 'new version'); if (mainWindow) mainWindow.webContents.send('update-ready'); }); // Handle install-update request from renderer ipcMain.on('install-update', () => { autoUpdater.quitAndInstall(); }); autoUpdater.on('update-not-available', () => { console.log('[AutoUpdater] App is up to date.'); if (mainWindow) mainWindow.webContents.send('update-not-available'); }); // Manual check from renderer ipcMain.on('check-for-updates', () => { try { autoUpdater.checkForUpdates(); } catch (e) { console.error('[AutoUpdater]', e.message); } }); autoUpdater.on('error', (err) => { console.error('[AutoUpdater] Error:', err.message); if (mainWindow) mainWindow.webContents.send('update-error', err.message); }); // Check for updates after 5 seconds, then every 30 minutes setTimeout(() => { try { autoUpdater.checkForUpdates(); } catch (e) { console.error('[AutoUpdater]', e.message); } }, 5000); setInterval(() => { try { autoUpdater.checkForUpdates(); } catch (e) { console.error('[AutoUpdater]', e.message); } }, 30 * 60 * 1000); } let mainWindow; function createWindow() { mainWindow = new BrowserWindow({ width: 1400, height: 900, minWidth: 900, minHeight: 600, title: 'Gaming Hub', icon: path.join(__dirname, 'assets', 'icon.png'), webPreferences: { preload: path.join(__dirname, 'preload.js'), contextIsolation: true, nodeIntegration: false, }, backgroundColor: '#1a1b1e', autoHideMenuBar: true, }); // Setup ad blocker BEFORE loading URL setupAdBlocker(session.defaultSession); // Enable screen capture (getDisplayMedia) in Electron — always show picker // IPC channel for picker result const PICKER_CHANNEL = 'screen-picker-result'; session.defaultSession.setDisplayMediaRequestHandler(async (_request, callback) => { const sources = await desktopCapturer.getSources({ types: ['screen', 'window'], thumbnailSize: { width: 320, height: 180 } }); if (sources.length === 0) { callback({}); return; } const sourceData = sources.map(s => ({ id: s.id, name: s.name, thumbnail: s.thumbnail.toDataURL(), })); // Write picker HTML to a temp file (data: URLs are blocked in Electron) const pickerHtml = `

Bildschirm oder Fenster w\\u00e4hlen

`; const tmpFile = path.join(os.tmpdir(), 'gaming-hub-screen-picker.html'); fs.writeFileSync(tmpFile, pickerHtml, 'utf-8'); const picker = new BrowserWindow({ width: 680, height: 520, parent: mainWindow, modal: true, resizable: false, minimizable: false, maximizable: false, title: 'Bildschirm / Fenster w\u00e4hlen', backgroundColor: '#1a1b1e', autoHideMenuBar: true, webPreferences: { contextIsolation: false, nodeIntegration: true, }, }); picker.loadFile(tmpFile); let resolved = false; const onPickerResult = (_event, selectedId) => { if (resolved) return; resolved = true; ipcMain.removeListener(PICKER_CHANNEL, onPickerResult); picker.close(); try { fs.unlinkSync(tmpFile); } catch {} if (!selectedId) { callback({}); return; } const chosen = sources.find(s => s.id === selectedId); if (chosen) { callback({ video: chosen, audio: 'loopback' }); } else { callback({}); } }; ipcMain.on(PICKER_CHANNEL, onPickerResult); picker.on('closed', () => { try { fs.unlinkSync(tmpFile); } catch {} if (!resolved) { resolved = true; ipcMain.removeListener(PICKER_CHANNEL, onPickerResult); callback({}); } }); }); // Custom User-Agent to identify Electron app const currentUA = mainWindow.webContents.getUserAgent(); mainWindow.webContents.setUserAgent(currentUA + ` GamingHubDesktop/${APP_VERSION}`); mainWindow.loadURL(HUB_URL); // Open external links in default browser mainWindow.webContents.setWindowOpenHandler(({ url }) => { if (!url.startsWith(HUB_URL)) { shell.openExternal(url); return { action: 'deny' }; } return { action: 'allow' }; }); // ── GOG OAuth: intercept the redirect in child windows ── mainWindow.webContents.on('did-create-window', (childWindow) => { childWindow.webContents.on('will-redirect', async (event, url) => { // GOG redirects to embed.gog.com/on_login_success?code=XXX after auth if (url.includes('on_login_success') && url.includes('code=')) { event.preventDefault(); try { const parsed = new URL(url); const code = parsed.searchParams.get('code') || ''; const state = parsed.searchParams.get('state') || ''; if (code) { // Exchange the code via our server const resp = await fetch(`${HUB_URL}/api/game-library/gog/exchange`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ code, linkTo: state }), }); const result = await resp.json(); if (resp.ok && result.ok) { childWindow.loadURL(`data:text/html,${encodeURIComponent(`

GOG verbunden!

${result.profileName}: ${result.gameCount} Spiele geladen.

`)}`); // Notify renderer to refresh profiles mainWindow.webContents.executeJavaScript('window.dispatchEvent(new Event("gog-connected"))'); setTimeout(() => { try { childWindow.close(); } catch {} }, 2500); } else { childWindow.loadURL(`data:text/html,${encodeURIComponent(`

Fehler

${result.error || 'Unbekannter Fehler'}

`)}`); } } } catch (err) { console.error('[GOG] Exchange error:', err); childWindow.loadURL(`data:text/html,${encodeURIComponent('

Fehler

GOG-Verbindung fehlgeschlagen.

')}`); } } }); // Allow child windows to navigate to GOG auth (don't open in external browser) childWindow.webContents.on('will-navigate', (event, url) => { if (url.startsWith('https://auth.gog.com') || url.startsWith('https://login.gog.com') || url.startsWith('https://embed.gog.com') || url.startsWith(HUB_URL)) { // Allow navigation within popup for GOG auth flow return; } }); }); // Handle navigation to external URLs mainWindow.webContents.on('will-navigate', (event, url) => { if (!url.startsWith(HUB_URL)) { event.preventDefault(); shell.openExternal(url); } }); // Track streaming status from renderer (synchronous — no async race) let isStreaming = false; ipcMain.on('streaming-status', (_event, active) => { isStreaming = active; }); // Windows toast notifications from renderer ipcMain.on('show-notification', (_event, title, body) => { if (Notification.isSupported()) { const notif = new Notification({ title, body, icon: path.join(__dirname, 'assets', 'icon.png') }); notif.show(); } }); // Warn before closing if a stream is active mainWindow.on('close', (event) => { if (!isStreaming) return; const result = dialog.showMessageBoxSync(mainWindow, { type: 'warning', buttons: ['Beenden', 'Abbrechen'], defaultId: 1, cancelId: 1, title: 'Stream laeuft noch!', message: 'Ein Stream ist noch aktiv.\nBeim Beenden wird der Stream gestoppt.', }); if (result !== 0) event.preventDefault(); }); } app.whenReady().then(() => { createWindow(); setupAutoUpdater(); }); app.on('window-all-closed', () => { if (process.platform !== 'darwin') app.quit(); }); app.on('activate', () => { if (BrowserWindow.getAllWindows().length === 0) createWindow(); });