Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
84 changes: 35 additions & 49 deletions eduaid_desktop/main.js
Original file line number Diff line number Diff line change
@@ -1,73 +1,65 @@
const { app, BrowserWindow, Menu, shell, dialog, ipcMain } = require('electron');
const path = require('path');
const fs = require('fs');
const config = require('./config');
const https = require('https');
const http = require('http');

// Keep a global reference of the window object
let mainWindow;

function createWindow() {
// Create the browser window using configuration
mainWindow = new BrowserWindow({
...config.windowOptions,
icon: config.window.icon,
webPreferences: config.window.webPreferences,
show: false, // Don't show until ready

// 🔥 FIX: Explicit preload path
webPreferences: {
...config.window.webPreferences,
preload: path.join(__dirname, 'preload.js'),
},

show: false,
titleBarStyle: config.window.titleBarStyle
});

// Determine which URL to load based on environment
if (!app.isPackaged) {
// In development, load from the dev server
mainWindow.loadURL(config.webUrl);
// Open DevTools in development
if (config.devTools) {
mainWindow.webContents.openDevTools();
}
} else {
mainWindow.loadFile(config.webPath);
}

// Show window when ready to prevent visual flash
mainWindow.once('ready-to-show', () => {
mainWindow.show();

// Focus on the window
if (config.isDevelopment) {
mainWindow.focus();
}
});

// Handle window closed
mainWindow.on('closed', () => {
mainWindow = null;
});

// Handle external links
mainWindow.webContents.setWindowOpenHandler(({ url }) => {
shell.openExternal(url);
return { action: 'deny' };
});

// Prevent navigation to external URLs
mainWindow.webContents.on('will-navigate', (event, navigationUrl) => {
const parsedUrl = new URL(navigationUrl);

// Check if the URL is in our allowed origins
const isAllowed = config.security.allowedOrigins.some(origin =>

const isAllowed = config.security.allowedOrigins.some(origin =>
parsedUrl.origin === origin || navigationUrl.startsWith(origin)
);

if (!isAllowed) {
event.preventDefault();
shell.openExternal(navigationUrl);
}
});
}

// Create application menu
function createMenu() {
const template = [
{
Expand All @@ -86,9 +78,7 @@ function createMenu() {
{
label: 'Quit',
accelerator: process.platform === 'darwin' ? 'Cmd+Q' : 'Ctrl+Q',
click: () => {
app.quit();
}
click: () => app.quit()
}
]
},
Expand Down Expand Up @@ -149,7 +139,6 @@ function createMenu() {
}
];

// macOS specific menu adjustments
if (process.platform === 'darwin') {
template.unshift({
label: app.getName(),
Expand All @@ -165,64 +154,53 @@ function createMenu() {
{ role: 'quit' }
]
});

// Window menu
template[4].submenu = [
{ role: 'close' },
{ role: 'minimize' },
{ role: 'zoom' },
{ type: 'separator' },
{ role: 'front' }
];
}

const menu = Menu.buildFromTemplate(template);
Menu.setApplicationMenu(menu);
}

// App event handlers
app.whenReady().then(() => {
createWindow();
createMenu();

app.on('activate', () => {
// On macOS, re-create window when dock icon is clicked
if (BrowserWindow.getAllWindows().length === 0) {
createWindow();
}
});
});

app.on('window-all-closed', () => {
// On macOS, keep app running even when all windows are closed
if (process.platform !== 'darwin') {
app.quit();
}
});

// Security: Prevent new window creation
app.on('web-contents-created', (event, contents) => {
contents.on('new-window', (event, navigationUrl) => {
event.preventDefault();
shell.openExternal(navigationUrl);
});
});

// Handle certificate errors
app.on('certificate-error', (event, webContents, url, error, certificate, callback) => {
if (url.startsWith('http://localhost:')) {
// Allow localhost in development
event.preventDefault();
callback(true);
} else {
callback(false);
}
});

// API request handler for the renderer process
ipcMain.handle('api-request', async (event, { endpoint, options }) => {

// ================================
// 🔥 API REQUEST HANDLER (Timeout Fix)
// ================================
ipcMain.handle('api-request', async (event, { endpoint, options = {} }) => {
return new Promise((resolve, reject) => {
const apiUrl = new URL(endpoint, config.apiUrl);

const requestOptions = {
method: options.method || 'GET',
headers: {
Expand All @@ -232,32 +210,40 @@ ipcMain.handle('api-request', async (event, { endpoint, options }) => {
};

const protocol = apiUrl.protocol === 'https:' ? https : http;

const req = protocol.request(apiUrl, requestOptions, (res) => {
let data = '';

res.on('data', (chunk) => {
data += chunk;
});

res.on('end', () => {
try {
const jsonData = JSON.parse(data);
const parsed = data ? JSON.parse(data) : null;

resolve({
ok: res.statusCode >= 200 && res.statusCode < 300,
status: res.statusCode,
data: jsonData
data: parsed
});
} catch (error) {
} catch (err) {
resolve({
ok: res.statusCode >= 200 && res.statusCode < 300,
ok: false,
status: res.statusCode,
data: data
data: null,
error: 'Invalid JSON response'
});
}
});
});

// 🔥 Timeout after 15 seconds
req.setTimeout(15000, () => {
req.destroy();
reject(new Error('Request timed out after 15 seconds'));
});

req.on('error', (error) => {
reject(error);
});
Expand All @@ -268,4 +254,4 @@ ipcMain.handle('api-request', async (event, { endpoint, options }) => {

req.end();
});
});
});
24 changes: 12 additions & 12 deletions eduaid_desktop/preload.js
Original file line number Diff line number Diff line change
@@ -1,19 +1,21 @@
const { contextBridge, ipcRenderer } = require("electron");
const config = require("./config");
const path = require("path");

// Expose protected methods that allow the renderer process to use
// the ipcRenderer without exposing the entire object
// 🔥 FIXED: Absolute path for config
const config = require(path.join(__dirname, "config.js"));

// Expose protected methods
contextBridge.exposeInMainWorld("electronAPI", {
// Platform information
platform: process.platform,

// API Configuration (NEW)
// API Configuration
getApiConfig: () => ({
baseUrl: config.apiUrl,
env: config.env,
}),

// Secure API Proxy (NEW)
// Secure API Proxy
makeApiRequest: async (endpoint, options) => {
return ipcRenderer.invoke("api-request", {
endpoint,
Expand All @@ -36,7 +38,7 @@ contextBridge.exposeInMainWorld("electronAPI", {
return process.env.npm_package_version || "1.0.0";
},

// File operations (if needed in the future)
// File operations
openFile: () => {
return ipcRenderer.invoke("dialog:openFile");
},
Expand All @@ -46,26 +48,24 @@ contextBridge.exposeInMainWorld("electronAPI", {
},
});

// DOM Content Loaded event
// DOM Ready
window.addEventListener("DOMContentLoaded", () => {
// Add any initialization code here
console.log("EduAid Desktop App loaded");

// Add desktop-specific styling or behavior
document.body.classList.add("electron-app");

// Handle keyboard shortcuts
document.addEventListener("keydown", (event) => {
// Prevent default browser shortcuts that don't make sense in desktop app
const isMod = event.ctrlKey || event.metaKey;
if (!isMod) return;

const key = event.key.toLowerCase();
const blockShortcuts = {
r: !event.shiftKey,
w: true,
};

if (blockShortcuts[key]) {
event.preventDefault();
}
});
});
});
Loading