const { app, BrowserWindow, ipcMain } = require('electron'); const path = require('path'); const os = require('os'); const spawn = require('child_process').spawn; const { store: electronStore } = require('./store'); const { setupIpcHandlers } = require('./ipc/handlers'); const { MeiliSearch } = require('meilisearch'); const slugify = (str: string) => { return str .toLowerCase() .replace(/[^a-z0-9]+/g, '-') .replace(/^-|-$/g, ''); }; const { platform } = require('os'); class MeilisearchService { private binaryPath: string; private serverProcess: any | null = null; private client: any | null = null; constructor() { this.binaryPath = this.getBinaryPath(); this.client = new MeiliSearch({ host: 'http://127.0.0.1:7700', apiKey: process.env.MEILISEARCH_MASTER_KEY || 'Damie', }); } private getBinaryPath(): string { const arch = process.arch; let binaryName: string; switch (platform()) { case 'darwin': binaryName = arch === 'arm64' ? 'meilisearch-macos-arm' : 'meilisearch-macos-x64'; break; case 'win32': binaryName = 'meilisearch-windows.exe'; break; case 'linux': binaryName = arch === 'arm' ? 'meilisearch-linux-arm' : 'meilisearch-linux-x64'; break; default: throw new Error(`Unsupported platform: ${platform()}`); } return path.join(__dirname, '..', 'meilisearch_binaries', binaryName); } public async startServer(): Promise { if (this.serverProcess) { console.log('Meilisearch server is already running.'); return; } try { const apiKey = 'Damie'; // Use the system's name as the API key process.env.MEILISEARCH_MASTER_KEY = apiKey; this.serverProcess = spawn(this.binaryPath, ['--http-addr', '127.0.0.1:7700'], { env: process.env, // Pass the environment variables to the child process }); this.serverProcess.stdout?.on('data', (data) => { console.log(`Meilisearch: ${data}`); }); this.serverProcess.stderr?.on('data', (data) => { console.error(`Meilisearch: ${data}`); }); this.serverProcess.on('close', (code) => { console.log(`Meilisearch server stopped with code ${code}`); this.serverProcess = null; }); this.serverProcess.on('error', (err) => { console.error('Failed to start Meilisearch server:', err); this.serverProcess = null; }); console.log('Meilisearch server started.'); } catch (error) { console.error('Failed to start Meilisearch server:', error); } } public async stopServer(): Promise { if (!this.serverProcess) { console.log('Meilisearch server is not running.'); return; } this.serverProcess.kill(); this.serverProcess = null; console.log('Meilisearch server stopped.'); } // Implement methods for adding, removing, and updating documents and collections // using the Meilisearch API. public async addDocuments(indexName: string, documents: any[]): Promise { // TODO: Implement this method console.log(`Adding documents to index ${indexName}`); } public async removeDocuments(indexName: string, documentIds: string[]): Promise { // TODO: Implement this method console.log(`Removing documents from index ${indexName}`); } public async updateDocuments(indexName: string, documents: any[]): Promise { // TODO: Implement this method console.log(`Updating documents in index ${indexName}`); } public async createIndex(indexName: string): Promise { const sluggedIndexName = slugify(indexName); // TODO: Implement this method console.log(`Creating index ${sluggedIndexName}`); try { await this.client.createIndex(sluggedIndexName); console.log(`Index ${sluggedIndexName} created successfully`); } catch (error) { console.error(`Failed to create index ${sluggedIndexName}:`, error); throw error; } } public async deleteIndex(indexName: string): Promise { try { await this.client.deleteIndex(indexName); console.log(`Index ${indexName} deleted successfully`); } catch (error) { console.error(`Failed to delete index ${indexName}:`, error); throw error; } } public async addDocuments(indexName: string, documents: any[]): Promise { try { const index = await this.client.index(indexName); await index.addDocuments(documents); console.log(`Documents added to index ${indexName} successfully`); } catch (error) { console.error(`Failed to add documents to index ${indexName}:`, error); throw error; } } public async removeDocuments(indexName: string, documentIds: string[]): Promise { try { const index = await this.client.index(indexName); await index.deleteDocuments(documentIds); console.log(`Documents removed from index ${indexName} successfully`); } catch (error) { console.error(`Failed to remove documents from index ${indexName}:`, error); throw error; } } public async updateDocuments(indexName: string, documents: any[]): Promise { try { const index = await this.client.index(indexName); await index.updateDocuments(documents); console.log(`Documents updated in index ${indexName} successfully`); } catch (error) { console.error(`Failed to update documents in index ${indexName}:`, error); throw error; } } } // Initialize IPC handlers immediately setupIpcHandlers(); const meilisearchService = new MeilisearchService(); (global as any).meilisearchService = meilisearchService; function createWindow() { const mainWindow = new BrowserWindow({ width: 1200, height: 800, minWidth: 1200, // Minimum width minHeight: 800, // Minimum height frame: false, titleBarStyle: 'hidden', webPreferences: { nodeIntegration: false, contextIsolation: true, preload: path.join(__dirname, 'preload.js'), sandbox: false, webSecurity: true, }, }); // Enable logging mainWindow.webContents.on('did-fail-load', (event, errorCode, errorDescription) => { console.error('Failed to load:', errorCode, errorDescription); }); mainWindow.webContents.on('console-message', (event, level, message) => { console.log('Renderer Console:', message); }); // In development, use the Vite dev server if (process.env.VITE_DEV_SERVER_URL) { // Wait for dev server to be ready const pollDevServer = async () => { try { const response = await fetch(process.env.VITE_DEV_SERVER_URL); if (response.ok) { mainWindow.loadURL(process.env.VITE_DEV_SERVER_URL); //mainWindow.webContents.openDevTools(); } else { setTimeout(pollDevServer, 500); } } catch { setTimeout(pollDevServer, 500); } }; pollDevServer(); } else { // In production, load the built files const prodPath = path.join(__dirname, '../dist/index.html'); console.log('Production mode detected'); console.log('__dirname:', __dirname); console.log('Attempting to load:', prodPath); // Check if the file exists try { if (require('fs').existsSync(prodPath)) { console.log('Found production build at:', prodPath); mainWindow.loadFile(prodPath).catch(err => { console.error('Failed to load production file:', err); // Try alternative path const altPath = path.join(process.cwd(), 'dist/index.html'); console.log('Trying alternative path:', altPath); if (require('fs').existsSync(altPath)) { mainWindow.loadFile(altPath).catch(err => { console.error('Failed to load alternative path:', err); }); } else { console.error('Alternative path does not exist'); } }); } else { console.error('Production build not found at:', prodPath); // Try alternative path const altPath = path.join(process.cwd(), 'dist/index.html'); console.log('Trying alternative path:', altPath); if (require('fs').existsSync(altPath)) { mainWindow.loadFile(altPath).catch(err => { console.error('Failed to load alternative path:', err); }); } else { console.error('Alternative path does not exist'); } } } catch (err) { console.error('Error checking file existence:', err); } } mainWindow.once('ready-to-show', () => { mainWindow.show(); }); // Handle window state const windowState = electronStore.get('windowState', { width: 1200, height: 800 }); mainWindow.setSize(windowState.width, windowState.height); mainWindow.on('close', () => { const { width, height } = mainWindow.getBounds(); electronStore.set('windowState', { width, height }); }); // Set up security headers mainWindow.webContents.session.webRequest.onHeadersReceived((details, callback) => { callback({ responseHeaders: { ...details.responseHeaders, 'Content-Security-Policy': [ "default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline'; img-src 'self' data:;" ] } }); }); } app.whenReady().then(() => { meilisearchService.startServer(); createWindow(); app.on('activate', () => { if (BrowserWindow.getAllWindows().length === 0) { createWindow(); } }); }); app.on('window-all-closed', () => { if (process.platform !== 'darwin') { app.quit(); } }); // IPC handlers for file system operations ipcMain.handle('get-app-path', () => { return app.getPath('userData'); }); // Window control handlers ipcMain.handle('window-minimize', (event) => { const window = BrowserWindow.fromWebContents(event.sender); window?.minimize(); }); ipcMain.handle('window-maximize', (event) => { const window = BrowserWindow.fromWebContents(event.sender); if (window?.isMaximized()) { window.unmaximize(); } else { window?.maximize(); } }); ipcMain.handle('window-close', (event) => { const window = BrowserWindow.fromWebContents(event.sender); window?.close(); }); ipcMain.handle('open-external', (_, url) => { return require('electron').shell.openExternal(url); }); // Launch the server if (process.env.NODE_ENV !== 'development') { const serverProcess = spawn('node', [path.join(__dirname, '../scripts/dev.cjs')], { stdio: 'inherit', }); serverProcess.on('close', (code) => { console.log(`Server process exited with code ${code}`); }); serverProcess.on('error', (err) => { console.error('Failed to start server process:', err); }); } app.on('will-quit', () => { meilisearchService.stopServer(); }); module.exports = app;