'use strict'; const { app, BrowserWindow, ipcMain, safeStorage, shell } = require('electron'); const http = require('node:http'); const path = require('node:path'); const { pathToFileURL } = require('node:url'); const { createSessionStore } = require('./src/session-store'); const { createAltaClient } = require('./src/alta-client'); const { createProxyManager } = require('./src/proxy-launch'); const { checkForUpdate } = require('./src/update-policy'); const { AppRuntime, PAIRING_ENVELOPE_FILENAME, PairingController, createBridgeHandler, } = require('./src/electron-runtime'); const BRIDGE_HOST = '127.0.0.1'; const BRIDGE_PORT = 18247; let mainWindow = null; let bridgeServer = null; let runtime = null; let pairingController = null; let firstRunPairingSecret = null; function getAppDirectory() { return app.isPackaged ? path.dirname(process.env.PORTABLE_EXECUTABLE_FILE || app.getPath('exe')) : __dirname; } function sendConnectionState() { if (mainWindow && !mainWindow.isDestroyed() && runtime) { mainWindow.webContents.send('connection-state-changed', runtime.getConnectionState()); } } function createWindow() { mainWindow = new BrowserWindow({ width: 1400, height: 900, icon: path.join(__dirname, 'assets', 'icon.png'), title: 'Alta Video Camera Proxy', webPreferences: { nodeIntegration: false, contextIsolation: true, sandbox: true, preload: path.join(__dirname, 'preload.js'), }, }); mainWindow.webContents.setWindowOpenHandler(() => ({ action: 'deny' })); mainWindow.webContents.on('will-navigate', (event) => event.preventDefault()); mainWindow.loadFile(path.join(__dirname, 'index.html')); if (process.argv.includes('--dev')) mainWindow.webContents.openDevTools(); } function isTrustedSender(event) { if (!mainWindow || mainWindow.isDestroyed() || !event) return false; const webContents = mainWindow.webContents; const frame = event.senderFrame; const expectedUrl = pathToFileURL(path.join(__dirname, 'index.html')).href; return event.sender === webContents && frame === webContents.mainFrame && frame.url === expectedUrl; } function registerIpc(channel, handler) { ipcMain.handle(channel, async (event, ...args) => { if (!isTrustedSender(event)) throw new Error('Forbidden IPC sender'); return handler(...args); }); } function registerIpcHandlers() { registerIpc('get-devices', () => runtime.getDevices()); registerIpc('get-device-sites', () => runtime.getDeviceSites()); registerIpc('get-auth-info', () => runtime.getAuthInfo()); registerIpc('launch-proxy', (deviceId) => runtime.launchProxy(deviceId)); registerIpc('stop-proxy', async (key) => { const result = await runtime.stopProxy(key); sendConnectionState(); return result; }); registerIpc('disconnect', () => { const state = runtime.disconnect(); sendConnectionState(); return state; }); registerIpc('get-connection-state', () => runtime.getConnectionState()); registerIpc('check-for-updates', () => runtime.checkForUpdates()); registerIpc('open-fixed-releases-page', () => runtime.openFixedReleasesPage()); registerIpc('rotate-pairing', () => { firstRunPairingSecret = null; return pairingController.rotate(); }); registerIpc('revoke-pairing', () => { firstRunPairingSecret = null; return pairingController.revoke(); }); registerIpc('get-pairing-status', () => { const result = pairingController.getStatus(); if (firstRunPairingSecret) { result.secret = firstRunPairingSecret; firstRunPairingSecret = null; } return result; }); } function startBridgeServer() { if (!pairingController.bridgeAuth) return; const handler = createBridgeHandler({ bridgeAuth: pairingController.bridgeAuth, sessionStore: runtime.sessionStore, onConnectionStateChanged: sendConnectionState, }); bridgeServer = http.createServer((request, response) => { handler(request, response).catch(() => { if (!response.writableEnded) { response.writeHead(500, { 'Content-Type': 'application/json', 'Cache-Control': 'no-store' }); response.end(JSON.stringify({ success: false, message: 'Bridge request failed' })); } }); }); bridgeServer.on('clientError', (_error, socket) => socket.destroy()); bridgeServer.on('error', (error) => { console.error(`Bridge server unavailable (${error.code || 'UNKNOWN'}).`); }); bridgeServer.listen(BRIDGE_PORT, BRIDGE_HOST); } app.whenReady().then(() => { const sessionStore = createSessionStore(); const altaClient = createAltaClient({ sessionStore }); const proxyManager = createProxyManager({ appDirectory: getAppDirectory() }); const secureStorageAvailable = safeStorage && safeStorage.isEncryptionAvailable(); pairingController = new PairingController({ envelopePath: path.join(app.getPath('userData'), PAIRING_ENVELOPE_FILENAME), protect: secureStorageAvailable ? (value) => safeStorage.encryptString(value) : undefined, unprotect: secureStorageAvailable ? (value) => safeStorage.decryptString(value) : undefined, }); const pairingState = pairingController.initialize(); firstRunPairingSecret = pairingState.secret || null; runtime = new AppRuntime({ sessionStore, altaClient, proxyManager, checkForUpdate, currentVersion: app.getVersion(), openExternal: (url) => shell.openExternal(url), }); registerIpcHandlers(); createWindow(); startBridgeServer(); }); app.on('before-quit', (event) => { if (runtime) { const state = runtime.disconnect(); if (state.activeProxies.length > 0) { event.preventDefault(); sendConnectionState(); return; } runtime.sessionStore.dispose(); } if (bridgeServer) bridgeServer.close(); }); app.on('window-all-closed', () => { if (process.platform !== 'darwin') app.quit(); }); app.on('activate', () => { if (BrowserWindow.getAllWindows().length === 0 && runtime) createWindow(); });