Update Electron main process for production readiness and app protocol handling

- Register `app://` protocol for secure file handling in production.
- Adjust icon paths to support macOS and Windows/Linux distinctions.
- Enhance security by validating file paths under the `out/` directory.
- Replace `file://` accesses with the `app://` protocol.
- Update `package.json` build scripts for platform-specific builds and refined output directory structure.
- Modify main and login window settings for improved compatibility across all platforms.
This commit is contained in:
natreex
2025-11-16 19:18:17 -05:00
parent de03dedaf0
commit a1fcca45cb
3 changed files with 93 additions and 37 deletions

View File

@@ -291,7 +291,7 @@ function ScribeContent() {
className="bg-background text-text-primary h-screen flex flex-col items-center justify-center font-['Lora']"> className="bg-background text-text-primary h-screen flex flex-col items-center justify-center font-['Lora']">
<div className="flex flex-col items-center space-y-6"> <div className="flex flex-col items-center space-y-6">
<div className="animate-pulse"> <div className="animate-pulse">
<img src="/logo.png" alt="ERitors Logo" style={{width: 400, height: 400}} /> <img src="/eritors-favicon-white.png" alt="ERitors Logo" style={{width: 400, height: 400}} />
</div> </div>
<div className="flex space-x-2"> <div className="flex space-x-2">
<div className="w-2 h-2 bg-primary rounded-full animate-bounce"></div> <div className="w-2 h-2 bg-primary rounded-full animate-bounce"></div>

View File

@@ -1,14 +1,30 @@
import { app, BrowserWindow, ipcMain } from 'electron'; import { app, BrowserWindow, ipcMain, nativeImage, protocol } from 'electron';
import * as path from 'path'; import * as path from 'path';
import * as url from 'url'; import * as url from 'url';
import { fileURLToPath } from 'url'; import { fileURLToPath } from 'url';
import Store from 'electron-store'; import Store from 'electron-store';
import * as fs from 'fs';
// Fix pour __dirname en ES modules // Fix pour __dirname en ES modules
const __filename = fileURLToPath(import.meta.url); const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename); const __dirname = path.dirname(__filename);
const isDev = process.env.NODE_ENV === 'development'; const isDev = !app.isPackaged;
// Enregistrer le protocole app:// comme standard (avant app.whenReady)
if (!isDev) {
protocol.registerSchemesAsPrivileged([
{
scheme: 'app',
privileges: {
standard: true,
secure: true,
supportFetchAPI: true,
corsEnabled: true
}
}
]);
}
// Définir le nom de l'application // Définir le nom de l'application
app.setName('ERitors Scribe'); app.setName('ERitors Scribe');
@@ -21,7 +37,9 @@ const preloadPath = isDev
// Icône de l'application // Icône de l'application
const iconPath = isDev const iconPath = isDev
? path.join(__dirname, '../build/icon.png') ? path.join(__dirname, '../build/icon.png')
: path.join(__dirname, '../build/icon.png'); : process.platform === 'darwin'
? path.join(process.resourcesPath, 'icon.icns') // macOS utilise .icns
: path.join(process.resourcesPath, 'app.asar/build/icon.png'); // Windows/Linux utilisent .png
// Store sécurisé pour le token // Store sécurisé pour le token
const store = new Store({ const store = new Store({
@@ -36,7 +54,8 @@ function createLoginWindow(): void {
width: 500, width: 500,
height: 900, height: 900,
resizable: false, resizable: false,
icon: iconPath, // Ne pas définir icon sur macOS - utilise l'icône de l'app bundle
...(process.platform !== 'darwin' && { icon: iconPath }),
webPreferences: { webPreferences: {
preload: preloadPath, preload: preloadPath,
contextIsolation: true, contextIsolation: true,
@@ -52,13 +71,7 @@ function createLoginWindow(): void {
loginWindow.loadURL(`http://localhost:${devPort}/login/login`); loginWindow.loadURL(`http://localhost:${devPort}/login/login`);
loginWindow.webContents.openDevTools(); loginWindow.webContents.openDevTools();
} else { } else {
loginWindow.loadURL( loginWindow.loadURL('app://./login/login/index.html');
url.format({
pathname: path.join(__dirname, '../out/login/login/index.html'),
protocol: 'file:',
slashes: true,
})
);
} }
loginWindow.once('ready-to-show', () => { loginWindow.once('ready-to-show', () => {
@@ -74,7 +87,8 @@ function createMainWindow(): void {
mainWindow = new BrowserWindow({ mainWindow = new BrowserWindow({
width: 1200, width: 1200,
height: 800, height: 800,
icon: iconPath, // Ne pas définir icon sur macOS - utilise l'icône de l'app bundle
...(process.platform !== 'darwin' && { icon: iconPath }),
webPreferences: { webPreferences: {
preload: preloadPath, preload: preloadPath,
contextIsolation: true, contextIsolation: true,
@@ -89,13 +103,7 @@ function createMainWindow(): void {
mainWindow.loadURL(`http://localhost:${devPort}`); mainWindow.loadURL(`http://localhost:${devPort}`);
mainWindow.webContents.openDevTools(); mainWindow.webContents.openDevTools();
} else { } else {
mainWindow.loadURL( mainWindow.loadURL('app://./index.html');
url.format({
pathname: path.join(__dirname, '../out/index.html'),
protocol: 'file:',
slashes: true,
})
);
} }
mainWindow.once('ready-to-show', () => { mainWindow.once('ready-to-show', () => {
@@ -143,13 +151,61 @@ ipcMain.on('logout', () => {
}); });
app.whenReady().then(() => { app.whenReady().then(() => {
console.log('App ready, isDev:', isDev);
console.log('resourcesPath:', process.resourcesPath);
console.log('isPackaged:', app.isPackaged);
// Enregistrer le protocole custom app:// pour servir les fichiers depuis out/
if (!isDev) {
const outPath = path.join(process.resourcesPath, 'app.asar.unpacked/out');
protocol.handle('app', async (request) => {
// Enlever app:// et ./
let filePath = request.url.replace('app://', '').replace(/^\.\//, '');
const fullPath = path.normalize(path.join(outPath, filePath));
// Vérifier que le chemin est bien dans out/ (sécurité)
if (!fullPath.startsWith(outPath)) {
console.error('Security: Attempted to access file outside out/:', fullPath);
return new Response('Forbidden', { status: 403 });
}
try {
const data = await fs.promises.readFile(fullPath);
const ext = path.extname(fullPath).toLowerCase();
const mimeTypes: Record<string, string> = {
'.html': 'text/html',
'.css': 'text/css',
'.js': 'application/javascript',
'.json': 'application/json',
'.png': 'image/png',
'.jpg': 'image/jpeg',
'.svg': 'image/svg+xml',
'.ico': 'image/x-icon',
'.woff': 'font/woff',
'.woff2': 'font/woff2',
'.ttf': 'font/ttf',
};
return new Response(data, {
headers: { 'Content-Type': mimeTypes[ext] || 'application/octet-stream' }
});
} catch (error) {
console.error('Failed to load:', fullPath, error);
return new Response('Not found', { status: 404 });
}
});
}
// Définir l'icône du Dock sur macOS // Définir l'icône du Dock sur macOS
if (process.platform === 'darwin' && app.dock) { if (process.platform === 'darwin' && app.dock) {
app.dock.setIcon(iconPath); const icon = nativeImage.createFromPath(iconPath);
app.dock.setIcon(icon);
} }
// Vérifier si un token existe // Vérifier si un token existe
const token = store.get('authToken'); const token = store.get('authToken');
console.log('Token exists:', !!token);
if (token) { if (token) {
// Token existe, ouvrir la fenêtre principale // Token existe, ouvrir la fenêtre principale
@@ -172,7 +228,6 @@ app.whenReady().then(() => {
}); });
app.on('window-all-closed', () => { app.on('window-all-closed', () => {
if (process.platform !== 'darwin') { // Quitter l'application quand toutes les fenêtres sont fermées
app.quit(); app.quit();
}
}); });

View File

@@ -5,17 +5,11 @@
"type": "module", "type": "module",
"main": "dist/electron/main.js", "main": "dist/electron/main.js",
"scripts": { "scripts": {
"dev:next": "next dev -p 4000", "dev": "concurrently \"next dev -p 4000\" \"wait-on http://localhost:4000 && electron -r tsx/cjs electron/main.ts\"",
"dev:electron": "NODE_ENV=development PORT=4000 electron -r tsx/cjs electron/main.ts", "build:mac": "next build && tsc --project tsconfig.electron.json && tsc --project tsconfig.preload.json && electron-builder build --mac",
"dev": "concurrently \"npm run dev:next\" \"wait-on http://localhost:4000 && npm run dev:electron\"", "build:win": "next build && tsc --project tsconfig.electron.json && tsc --project tsconfig.preload.json && electron-builder build --win",
"build:next": "next build", "build:linux": "next build && tsc --project tsconfig.electron.json && tsc --project tsconfig.preload.json && electron-builder build --linux",
"build:electron": "tsc --project tsconfig.electron.json && tsc --project tsconfig.preload.json", "build:all": "next build && tsc --project tsconfig.electron.json && tsc --project tsconfig.preload.json && electron-builder build --mac --win --linux"
"build": "npm run build:next && npm run build:electron",
"start": "electron .",
"package": "npm run build && electron-builder build --mac --win --linux",
"package:mac": "npm run build && electron-builder build --mac",
"package:win": "npm run build && electron-builder build --win",
"package:linux": "npm run build && electron-builder build --linux"
}, },
"keywords": [], "keywords": [],
"author": "", "author": "",
@@ -71,17 +65,22 @@
"tailwindcss": "^4.1.17" "tailwindcss": "^4.1.17"
}, },
"build": { "build": {
"appId": "com.eritorsscribe.app", "appId": "com.eritors.scribe.desktop",
"productName": "EritorsScribe", "productName": "ERitors Scribe",
"files": [ "files": [
"dist/**/*", "dist/**/*",
"out/**/*", "out/**/*",
"build/**/*",
"package.json" "package.json"
], ],
"asarUnpack": [
"out/**/*"
],
"directories": { "directories": {
"output": "release" "output": "release"
}, },
"mac": { "mac": {
"icon": "build/icons/mac/icon.icns",
"target": [ "target": [
"dmg", "dmg",
"zip" "zip"
@@ -93,6 +92,7 @@
"entitlementsInherit": "build/entitlements.mac.plist" "entitlementsInherit": "build/entitlements.mac.plist"
}, },
"win": { "win": {
"icon": "build/icons/win/icon.ico",
"target": [ "target": [
{ {
"target": "nsis", "target": "nsis",
@@ -104,6 +104,7 @@
] ]
}, },
"linux": { "linux": {
"icon": "build/icons/png",
"target": [ "target": [
"AppImage", "AppImage",
"deb" "deb"