diff --git a/eduaid_desktop/main.js b/eduaid_desktop/main.js index b0346138..2148cc22 100644 --- a/eduaid_desktop/main.js +++ b/eduaid_desktop/main.js @@ -1,28 +1,28 @@ 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(); } @@ -30,36 +30,29 @@ function createWindow() { 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); @@ -67,7 +60,6 @@ function createWindow() { }); } -// Create application menu function createMenu() { const template = [ { @@ -86,9 +78,7 @@ function createMenu() { { label: 'Quit', accelerator: process.platform === 'darwin' ? 'Cmd+Q' : 'Ctrl+Q', - click: () => { - app.quit(); - } + click: () => app.quit() } ] }, @@ -149,7 +139,6 @@ function createMenu() { } ]; - // macOS specific menu adjustments if (process.platform === 'darwin') { template.unshift({ label: app.getName(), @@ -165,28 +154,17 @@ 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(); } @@ -194,13 +172,11 @@ app.whenReady().then(() => { }); 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(); @@ -208,10 +184,8 @@ app.on('web-contents-created', (event, contents) => { }); }); -// 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 { @@ -219,10 +193,14 @@ app.on('certificate-error', (event, webContents, url, error, certificate, callba } }); -// 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: { @@ -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); }); @@ -268,4 +254,4 @@ ipcMain.handle('api-request', async (event, { endpoint, options }) => { req.end(); }); -}); +}); \ No newline at end of file diff --git a/eduaid_desktop/preload.js b/eduaid_desktop/preload.js index 080f3dd8..f43a91ff 100644 --- a/eduaid_desktop/preload.js +++ b/eduaid_desktop/preload.js @@ -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, @@ -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"); }, @@ -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(); } }); -}); +}); \ No newline at end of file diff --git a/eduaid_web/src/pages/Home.jsx b/eduaid_web/src/pages/Home.jsx index e00a0d23..832a5eb6 100644 --- a/eduaid_web/src/pages/Home.jsx +++ b/eduaid_web/src/pages/Home.jsx @@ -1,6 +1,6 @@ import React, { useState, useEffect } from "react"; import "../index.css"; -import logo_trans from "../assets/aossie_logo_transparent.png" +import logo_trans from "../assets/aossie_logo_transparent.png"; import starsImg from "../assets/stars.png"; import arrow from "../assets/arrow.png"; import gitStar from "../assets/gitStar.png"; @@ -12,9 +12,21 @@ const Home = () => { const [error, setError] = useState(""); async function fetchGitHubStars() { - const response = await fetch("https://api.github.com/repos/AOSSIE-Org/EduAid"); - if (!response.ok) throw new Error("Failed to fetch stars"); + const response = await fetch( + "https://api.github.com/repos/AOSSIE-Org/EduAid" + ); + const data = await response.json(); + + if ( + !response.ok || + !data || + typeof data.stargazers_count !== "number" + ) { + console.error("Invalid GitHub API response:", data); + throw new Error("Invalid GitHub response"); + } + return data.stargazers_count; } @@ -27,24 +39,36 @@ const Home = () => { const storedStars = localStorage.getItem("stars"); const storedTime = localStorage.getItem("fetchTime"); - if (storedStars && storedTime && !isMoreThanOneDayOld(parseInt(storedTime))) { - setStars(parseInt(storedStars)); - } else { - fetchGitHubStars() - .then((starCount) => { - setStars(starCount); - localStorage.setItem("stars", starCount); - localStorage.setItem("fetchTime", Date.now().toString()); - }) - .catch(() => setError("Failed to fetch stars")); + if (storedStars !== null && storedTime !== null) { + const cachedStars = Number(storedStars); + const cachedTime = Number(storedTime); + + if ( + Number.isFinite(cachedStars) && + Number.isFinite(cachedTime) && + !isMoreThanOneDayOld(cachedTime) + ) { + setStars(cachedStars); + return; + } } + + fetchGitHubStars() + .then((starCount) => { + setStars(starCount); + localStorage.setItem("stars", String(starCount)); + localStorage.setItem("fetchTime", Date.now().toString()); + }) + .catch(() => setError("Failed to fetch stars")); }, []); return ( -
A tool that can auto-generate short quizzes
-based on user input
-Turn your notes into interactive quizzes in seconds.
++ Upload your PDF or DOC file and start practicing instantly. +
{step}
+{point}
+Choose a file (PDF, MP3 supported)
++ Choose a file (PDF supported) +
+ + -