361 lines
11 KiB
TypeScript
Raw Normal View History

2025-02-04 12:45:17 -05:00
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');
2025-02-09 02:09:16 -05:00
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<void> {
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<void> {
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<void> {
// TODO: Implement this method
console.log(`Adding documents to index ${indexName}`);
}
public async removeDocuments(indexName: string, documentIds: string[]): Promise<void> {
// TODO: Implement this method
console.log(`Removing documents from index ${indexName}`);
}
public async updateDocuments(indexName: string, documents: any[]): Promise<void> {
// TODO: Implement this method
console.log(`Updating documents in index ${indexName}`);
}
public async createIndex(indexName: string): Promise<void> {
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<void> {
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<void> {
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<void> {
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<void> {
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;
}
}
}
2025-02-04 01:30:36 -05:00
// Initialize IPC handlers immediately
setupIpcHandlers();
2025-02-09 02:09:16 -05:00
const meilisearchService = new MeilisearchService();
(global as any).meilisearchService = meilisearchService;
2025-02-04 01:30:36 -05:00
function createWindow() {
const mainWindow = new BrowserWindow({
width: 1200,
height: 800,
2025-02-05 10:05:22 -05:00
minWidth: 1200, // Minimum width
minHeight: 800, // Minimum height
2025-02-04 01:30:36 -05:00
frame: false,
titleBarStyle: 'hidden',
webPreferences: {
nodeIntegration: false,
contextIsolation: true,
preload: path.join(__dirname, 'preload.js'),
sandbox: false,
webSecurity: true,
},
2025-02-04 12:45:17 -05:00
});
// 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);
2025-02-04 01:30:36 -05:00
});
// 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);
2025-02-05 10:05:22 -05:00
//mainWindow.webContents.openDevTools();
2025-02-04 01:30:36 -05:00
} else {
setTimeout(pollDevServer, 500);
}
} catch {
setTimeout(pollDevServer, 500);
}
};
pollDevServer();
} else {
// In production, load the built files
2025-02-04 12:45:17 -05:00
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);
}
2025-02-04 01:30:36 -05:00
}
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(() => {
2025-02-09 02:09:16 -05:00
meilisearchService.startServer();
2025-02-04 01:30:36 -05:00
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();
});
2025-02-04 12:45:17 -05:00
ipcMain.handle('open-external', (_, url) => {
return require('electron').shell.openExternal(url);
});
2025-02-09 02:09:16 -05:00
// 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();
});
2025-02-04 12:45:17 -05:00
module.exports = app;