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';
// Separate URL for auto-updates (no Pangolin auth, publicly accessible)
const UPDATE_URL = process.env.GAMING_HUB_UPDATE_URL || 'https://updates.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.
// Uses separate subdomain without Pangolin auth (Squirrel has no browser session).
const updateURL = `${UPDATE_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 code via renderer fetch (has Pangolin session cookie)
const jsCode = `
fetch('/api/game-library/gog/exchange', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ code: ${JSON.stringify(code)}, linkTo: ${JSON.stringify(state)} }),
}).then(r => r.json()).then(result => {
window.dispatchEvent(new Event('gog-connected'));
return result;
})
`;
const result = await mainWindow.webContents.executeJavaScript(jsCode);
if (result && result.ok) {
childWindow.loadURL(`data:text/html,${encodeURIComponent(`GOG verbunden!
${result.profileName}: ${result.gameCount} Spiele geladen.
`)}`);
setTimeout(() => { try { childWindow.close(); } catch {} }, 2500);
} else {
childWindow.loadURL(`data:text/html,${encodeURIComponent(`Fehler
${(result && 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();
});