const { app, BrowserWindow, protocol, ipcMain } = require('electron'); const path = require('path'); app.commandLine.appendSwitch('enable-experimental-web-platform-features'); app.commandLine.appendSwitch('enable-web-bluetooth'); app.commandLine.appendSwitch('enable-features', 'WebBluetooth'); if (process.platform === 'linux') { app.commandLine.appendSwitch('disable-gpu-sandbox'); app.commandLine.appendSwitch('no-sandbox'); app.commandLine.appendSwitch('disable-seccomp-filter-sandbox'); app.commandLine.appendSwitch('enable-logging'); app.commandLine.appendSwitch('v', '1'); } const isDev = process.env.ELECTRON_IS_DEV === '1'; let mainWindow; let deepLinkUrl = null; let bluetoothSelectCallback = null; let discoveredDevices = new Map(); const gotTheLock = app.requestSingleInstanceLock(); if (!gotTheLock) { app.quit(); } else { app.on('second-instance', (event, commandLine) => { if (mainWindow) { if (mainWindow.isMinimized()) mainWindow.restore(); mainWindow.focus(); const url = commandLine.find(arg => arg.startsWith('rideaware://')); if (url) { handleDeepLink(url); } } }); app.whenReady().then(() => { if (!isDev) { protocol.registerFileProtocol('rideaware', (request, callback) => { callback({ path: '' }); }); } createWindow(); app.on('open-url', (event, url) => { event.preventDefault(); handleDeepLink(url); }); if (deepLinkUrl) { handleDeepLink(deepLinkUrl); deepLinkUrl = null; } }); } app.on('open-url', (event, url) => { event.preventDefault(); if (mainWindow) { handleDeepLink(url); } else { deepLinkUrl = url; } }); if (process.platform !== 'darwin') { const url = process.argv.find(arg => arg.startsWith('rideaware://')); if (url) { deepLinkUrl = url; } } function createWindow() { mainWindow = new BrowserWindow({ width: 1200, height: 900, minWidth: 800, minHeight: 600, webPreferences: { preload: path.join(__dirname, 'preload.cjs'), nodeIntegration: false, contextIsolation: true, enableBlinkFeatures: 'WebBluetooth', webSecurity: true, sandbox: process.platform !== 'linux', }, backgroundColor: '#0a0a0a', show: false, }); mainWindow.webContents.session.setPermissionCheckHandler((webContents, permission) => { if (permission === 'bluetooth') { return true; } return false; }); mainWindow.webContents.session.setPermissionRequestHandler((webContents, permission, callback) => { if (permission === 'bluetooth') { callback(true); } else { callback(false); } }); mainWindow.once('ready-to-show', () => { mainWindow.show(); }); if (isDev) { mainWindow.loadURL('http://localhost:5173'); mainWindow.webContents.openDevTools(); } else { mainWindow.loadFile(path.join(__dirname, '../dist/index.html')); } mainWindow.on('closed', () => { mainWindow = null; }); mainWindow.webContents.setWindowOpenHandler(({ url }) => { require('electron').shell.openExternal(url); return { action: 'deny' }; }); mainWindow.webContents.on('select-bluetooth-device', (event, deviceList, callback) => { event.preventDefault(); bluetoothSelectCallback = callback; deviceList.forEach((device) => { discoveredDevices.set(device.deviceId, { id: device.deviceId, name: device.deviceName || 'Unknown Device', }); }); const devices = Array.from(discoveredDevices.values()); mainWindow.webContents.send('bluetooth:devices-updated', devices); }); } ipcMain.on('bluetooth:select-device', (event, deviceId) => { if (bluetoothSelectCallback) { bluetoothSelectCallback(deviceId); bluetoothSelectCallback = null; } }); ipcMain.on('bluetooth:cancel-selection', () => { if (bluetoothSelectCallback) { bluetoothSelectCallback(''); bluetoothSelectCallback = null; } discoveredDevices.clear(); }); ipcMain.on('bluetooth:clear-devices', () => { discoveredDevices.clear(); }); function handleDeepLink(urlString) { if (!mainWindow) return; try { const parsedUrl = new URL(urlString); if (parsedUrl.protocol === 'rideaware:') { const action = parsedUrl.hostname; const params = {}; parsedUrl.searchParams.forEach((value, key) => { params[key] = value; }); mainWindow.webContents.send('deep-link', { action, params, }); if (mainWindow.isMinimized()) mainWindow.restore(); mainWindow.focus(); } } catch (error) { console.error('Failed to parse deep link:', error); } } ipcMain.handle('get-version', () => { return app.getVersion(); }); ipcMain.handle('get-platform', () => { return process.platform; }); ipcMain.handle('bluetooth:check-availability', async () => { return { available: true, platform: process.platform, flags: { webBluetooth: app.commandLine.hasSwitch('enable-web-bluetooth'), experimentalFeatures: app.commandLine.hasSwitch('enable-experimental-web-platform-features'), } }; }); ipcMain.handle('bluetooth:check-system', async () => { const { exec } = require('child_process'); const { promisify } = require('util'); const execAsync = promisify(exec); const result = { platform: process.platform, available: false, adapter: null, error: null, guidance: [] }; try { if (process.platform === 'linux') { try { const { stdout } = await execAsync('systemctl is-active bluetooth'); result.available = stdout.trim() === 'active'; if (result.available) { try { const { stdout: hciOutput } = await execAsync('hciconfig 2>&1'); result.adapter = hciOutput.includes('UP RUNNING') ? 'enabled' : 'disabled'; if (!hciOutput.includes('UP RUNNING')) { result.guidance.push('Bluetooth adapter is not powered on. Try: sudo hciconfig hci0 up'); } } catch { result.adapter = 'unknown'; result.guidance.push('Unable to check Bluetooth adapter status. Install bluez-utils if not present.'); } } else { result.guidance.push('Bluetooth service is not running. Start it with: sudo systemctl start bluetooth'); } } catch { result.available = false; result.guidance.push('Bluetooth service not found. Install bluez package.'); } } else if (process.platform === 'darwin') { result.available = true; result.adapter = 'system'; } else if (process.platform === 'win32') { result.available = true; result.adapter = 'system'; } } catch (error) { result.error = error.message; } return result; }); ipcMain.handle('minimize-window', () => { if (mainWindow) mainWindow.minimize(); }); ipcMain.handle('maximize-window', () => { if (mainWindow) { if (mainWindow.isMaximized()) { mainWindow.unmaximize(); } else { mainWindow.maximize(); } } }); ipcMain.handle('close-window', () => { if (mainWindow) mainWindow.close(); }); app.on('window-all-closed', () => { if (process.platform !== 'darwin') { app.quit(); } }); app.on('activate', () => { if (BrowserWindow.getAllWindows().length === 0) { createWindow(); } });