Compare commits
2 Commits
master
...
electron_f
Author | SHA1 | Date |
---|---|---|
|
ae25a066f2 | 7 days ago |
|
4230deab1d | 2 weeks ago |
13 changed files with 672 additions and 222 deletions
@ -0,0 +1,36 @@ |
|||||
|
{ |
||||
|
"appId": "app.timesafari.app", |
||||
|
"productName": "TimeSafari", |
||||
|
"directories": { |
||||
|
"output": "dist-electron-packages", |
||||
|
"buildResources": "build" |
||||
|
}, |
||||
|
"files": [ |
||||
|
"dist-electron/**/*", |
||||
|
"node_modules/**/*", |
||||
|
"package.json", |
||||
|
"src/electron/electron-logger.js" |
||||
|
], |
||||
|
"extraResources": [ |
||||
|
{ |
||||
|
"from": "src/utils", |
||||
|
"to": "utils", |
||||
|
"filter": ["**/*"] |
||||
|
} |
||||
|
], |
||||
|
"extraMetadata": { |
||||
|
"main": "src/electron/main.js" |
||||
|
}, |
||||
|
"linux": { |
||||
|
"target": ["AppImage"], |
||||
|
"category": "Utility", |
||||
|
"maintainer": "TimeSafari Team" |
||||
|
}, |
||||
|
"mac": { |
||||
|
"target": ["dmg"], |
||||
|
"category": "public.app-category.productivity" |
||||
|
}, |
||||
|
"win": { |
||||
|
"target": ["nsis"] |
||||
|
} |
||||
|
} |
@ -0,0 +1,177 @@ |
|||||
|
#!/usr/bin/env node
|
||||
|
|
||||
|
/** |
||||
|
* @file check-electron-prerequisites.js |
||||
|
* @description Verifies and installs required dependencies for Electron builds |
||||
|
* |
||||
|
* This script checks if Python's distutils module is available, which is required |
||||
|
* by node-gyp when compiling native Node.js modules during Electron packaging. |
||||
|
* Without distutils, builds will fail with "ModuleNotFoundError: No module named 'distutils'". |
||||
|
* |
||||
|
* The script performs the following actions: |
||||
|
* 1. Checks if Python's distutils module is available |
||||
|
* 2. If missing, offers to install setuptools package which provides distutils |
||||
|
* 3. Attempts installation through pip or pip3 |
||||
|
* 4. Provides manual installation instructions if automated installation fails |
||||
|
* |
||||
|
* Usage: |
||||
|
* - Direct execution: node scripts/check-electron-prerequisites.js |
||||
|
* - As npm script: npm run check:electron |
||||
|
* - Before builds: npm run check:electron && electron-builder |
||||
|
* |
||||
|
* Exit codes: |
||||
|
* - 0: All prerequisites are met or were successfully installed |
||||
|
* - 1: Prerequisites are missing and weren't installed |
||||
|
* |
||||
|
* @author [YOUR_NAME] |
||||
|
* @version 1.0.0 |
||||
|
* @license MIT |
||||
|
*/ |
||||
|
|
||||
|
const { execSync } = require('child_process'); |
||||
|
const readline = require('readline'); |
||||
|
const chalk = require('chalk'); // You might need to add this to your dependencies
|
||||
|
|
||||
|
console.log(chalk.blue('🔍 Checking Electron build prerequisites...')); |
||||
|
|
||||
|
/** |
||||
|
* Checks if Python's distutils module is available |
||||
|
* |
||||
|
* This function attempts to import the distutils module in Python. |
||||
|
* If successful, it means node-gyp will be able to compile native modules. |
||||
|
* If unsuccessful, the Electron build will likely fail when compiling native dependencies. |
||||
|
* |
||||
|
* @returns {boolean} True if distutils is available, false otherwise |
||||
|
* |
||||
|
* @example |
||||
|
* if (checkDistutils()) { |
||||
|
* console.log('Ready to build Electron app'); |
||||
|
* } |
||||
|
*/ |
||||
|
function checkDistutils() { |
||||
|
try { |
||||
|
// Attempt to import distutils using Python
|
||||
|
// We use stdio: 'ignore' to suppress any Python output
|
||||
|
execSync('python -c "import distutils"', { stdio: 'ignore' }); |
||||
|
console.log(chalk.green('✅ Python distutils is available')); |
||||
|
return true; |
||||
|
} catch (e) { |
||||
|
// This error occurs if either Python is not found or if distutils is missing
|
||||
|
console.log(chalk.red('❌ Python distutils is missing')); |
||||
|
return false; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Installs the setuptools package which provides distutils |
||||
|
* |
||||
|
* This function attempts to install setuptools using pip or pip3. |
||||
|
* Setuptools is a package that provides the distutils module needed by node-gyp. |
||||
|
* In Python 3.12+, distutils was moved out of the standard library into setuptools. |
||||
|
* |
||||
|
* The function tries multiple installation methods: |
||||
|
* 1. First attempts with pip |
||||
|
* 2. If that fails, tries with pip3 |
||||
|
* 3. If both fail, provides instructions for manual installation |
||||
|
* |
||||
|
* @returns {Promise<boolean>} True if installation succeeded, false otherwise |
||||
|
* |
||||
|
* @example |
||||
|
* const success = await installSetuptools(); |
||||
|
* if (success) { |
||||
|
* console.log('Ready to proceed with build'); |
||||
|
* } else { |
||||
|
* console.log('Please fix prerequisites manually'); |
||||
|
* } |
||||
|
*/ |
||||
|
async function installSetuptools() { |
||||
|
console.log(chalk.yellow('📦 Attempting to install setuptools...')); |
||||
|
|
||||
|
try { |
||||
|
// First try with pip, commonly used on all platforms
|
||||
|
execSync('pip install setuptools', { stdio: 'inherit' }); |
||||
|
console.log(chalk.green('✅ Successfully installed setuptools')); |
||||
|
return true; |
||||
|
} catch (pipError) { |
||||
|
try { |
||||
|
// If pip fails, try with pip3 (common on Linux distributions)
|
||||
|
console.log(chalk.yellow('⚠️ Trying with pip3...')); |
||||
|
execSync('pip3 install setuptools', { stdio: 'inherit' }); |
||||
|
console.log(chalk.green('✅ Successfully installed setuptools using pip3')); |
||||
|
return true; |
||||
|
} catch (pip3Error) { |
||||
|
// If both methods fail, provide manual installation guidance
|
||||
|
console.log(chalk.red('❌ Failed to install setuptools automatically')); |
||||
|
console.log(chalk.yellow('📝 Please install it manually with:')); |
||||
|
console.log(' pip install setuptools'); |
||||
|
console.log(' or'); |
||||
|
console.log(' sudo apt install python3-setuptools (on Debian/Ubuntu)'); |
||||
|
console.log(' sudo pacman -S python-setuptools (on Arch Linux)'); |
||||
|
console.log(' sudo dnf install python3-setuptools (on Fedora)'); |
||||
|
console.log(' brew install python-setuptools (on macOS with Homebrew)'); |
||||
|
return false; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Main execution function |
||||
|
* |
||||
|
* This function orchestrates the checking and installation process: |
||||
|
* 1. Checks if distutils is already available |
||||
|
* 2. If not, informs the user and prompts for installation |
||||
|
* 3. Based on user input, attempts to install or exits |
||||
|
* |
||||
|
* The function handles interactive user prompts and orchestrates |
||||
|
* the overall flow of the script. |
||||
|
* |
||||
|
* @returns {Promise<void>} |
||||
|
* @throws Will exit process with code 1 if prerequisites aren't met |
||||
|
*/ |
||||
|
async function main() { |
||||
|
// First check if distutils is already available
|
||||
|
if (checkDistutils()) { |
||||
|
// All prerequisites are met, exit successfully
|
||||
|
process.exit(0); |
||||
|
} |
||||
|
|
||||
|
// Inform the user about the missing prerequisite
|
||||
|
console.log(chalk.yellow('⚠️ Python distutils is required for Electron builds')); |
||||
|
console.log(chalk.yellow('⚠️ This is needed to compile native modules during the build process')); |
||||
|
|
||||
|
// Set up readline interface for user interaction
|
||||
|
const rl = readline.createInterface({ |
||||
|
input: process.stdin, |
||||
|
output: process.stdout |
||||
|
}); |
||||
|
|
||||
|
// Prompt the user for installation permission
|
||||
|
const answer = await new Promise(resolve => { |
||||
|
rl.question(chalk.blue('Would you like to install setuptools now? (y/n) '), resolve); |
||||
|
}); |
||||
|
|
||||
|
// Clean up readline interface
|
||||
|
rl.close(); |
||||
|
|
||||
|
if (answer.toLowerCase() === 'y') { |
||||
|
// User agreed to installation
|
||||
|
const success = await installSetuptools(); |
||||
|
if (success) { |
||||
|
// Installation succeeded, exit successfully
|
||||
|
process.exit(0); |
||||
|
} else { |
||||
|
// Installation failed, exit with error
|
||||
|
process.exit(1); |
||||
|
} |
||||
|
} else { |
||||
|
// User declined installation
|
||||
|
console.log(chalk.yellow('⚠️ Build may fail without distutils')); |
||||
|
process.exit(1); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// Execute the main function and handle any uncaught errors
|
||||
|
main().catch(error => { |
||||
|
console.error(chalk.red('Error during prerequisites check:'), error); |
||||
|
process.exit(1); |
||||
|
}); |
@ -0,0 +1,61 @@ |
|||||
|
/** |
||||
|
* Fix path resolution issues in the Electron build |
||||
|
*/ |
||||
|
const fs = require('fs'); |
||||
|
const path = require('path'); |
||||
|
const glob = require('glob'); |
||||
|
|
||||
|
// Fix asset paths in HTML file
|
||||
|
function fixHtmlPaths() { |
||||
|
const htmlFile = path.join(__dirname, '../dist-electron/index.html'); |
||||
|
if (fs.existsSync(htmlFile)) { |
||||
|
let html = fs.readFileSync(htmlFile, 'utf8'); |
||||
|
|
||||
|
// Convert absolute paths to relative
|
||||
|
html = html.replace(/src="\//g, 'src="./'); |
||||
|
html = html.replace(/href="\//g, 'href="./'); |
||||
|
|
||||
|
fs.writeFileSync(htmlFile, html); |
||||
|
console.log('✅ Fixed paths in index.html'); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// Fix asset imports in JS files
|
||||
|
function fixJsPaths() { |
||||
|
const jsFiles = glob.sync('dist-electron/assets/*.js'); |
||||
|
|
||||
|
jsFiles.forEach(file => { |
||||
|
let content = fs.readFileSync(file, 'utf8'); |
||||
|
|
||||
|
// Replace absolute imports with relative ones
|
||||
|
const originalContent = content; |
||||
|
content = content.replace(/["']\/assets\//g, '"./assets/'); |
||||
|
|
||||
|
if (content !== originalContent) { |
||||
|
fs.writeFileSync(file, content); |
||||
|
console.log(`✅ Fixed paths in ${path.basename(file)}`); |
||||
|
} |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
// Add base href to HTML
|
||||
|
function addBaseHref() { |
||||
|
const htmlFile = path.join(__dirname, '../dist-electron/index.html'); |
||||
|
if (fs.existsSync(htmlFile)) { |
||||
|
let html = fs.readFileSync(htmlFile, 'utf8'); |
||||
|
|
||||
|
// Add base href if not present
|
||||
|
if (!html.includes('<base href=')) { |
||||
|
html = html.replace('</head>', '<base href="./">\n</head>'); |
||||
|
fs.writeFileSync(htmlFile, html); |
||||
|
console.log('✅ Added base href to index.html'); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// Run all fixes
|
||||
|
fixHtmlPaths(); |
||||
|
fixJsPaths(); |
||||
|
addBaseHref(); |
||||
|
|
||||
|
console.log('🎉 Electron path fixes completed'); |
@ -0,0 +1,14 @@ |
|||||
|
// This is a placeholder notarize script that does nothing for non-macOS platforms
|
||||
|
// Only necessary for macOS app store submissions
|
||||
|
|
||||
|
exports.default = async function notarizing(context) { |
||||
|
// Only notarize macOS builds
|
||||
|
if (context.electronPlatformName !== 'darwin') { |
||||
|
console.log('Skipping notarization for non-macOS platform'); |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
// For macOS, we would implement actual notarization here
|
||||
|
console.log('This is where macOS notarization would happen'); |
||||
|
// We're just returning with no action for non-macOS builds
|
||||
|
}; |
@ -0,0 +1,37 @@ |
|||||
|
/** |
||||
|
* Electron-specific logger implementation |
||||
|
*/ |
||||
|
const fs = require("fs"); |
||||
|
const path = require("path"); |
||||
|
const { app } = require("electron"); |
||||
|
|
||||
|
// Create logs directory if it doesn't exist
|
||||
|
const logsDir = path.join(app.getPath("userData"), "logs"); |
||||
|
if (!fs.existsSync(logsDir)) { |
||||
|
fs.mkdirSync(logsDir, { recursive: true }); |
||||
|
} |
||||
|
|
||||
|
const logFile = path.join( |
||||
|
logsDir, |
||||
|
`electron-${new Date().toISOString().split("T")[0]}.log`, |
||||
|
); |
||||
|
|
||||
|
function log(level, message) { |
||||
|
const timestamp = new Date().toISOString(); |
||||
|
const logMessage = `[${timestamp}] [${level}] ${message}\n`; |
||||
|
|
||||
|
// Write to log file
|
||||
|
fs.appendFileSync(logFile, logMessage); |
||||
|
|
||||
|
// Also output to console
|
||||
|
// eslint-disable-next-line no-console
|
||||
|
console[level.toLowerCase()](message); |
||||
|
} |
||||
|
|
||||
|
module.exports = { |
||||
|
info: (message) => log("INFO", message), |
||||
|
warn: (message) => log("WARN", message), |
||||
|
error: (message) => log("ERROR", message), |
||||
|
debug: (message) => log("DEBUG", message), |
||||
|
getLogPath: () => logFile, |
||||
|
}; |
@ -1,174 +1,236 @@ |
|||||
const { app, BrowserWindow } = require("electron"); |
const { app, BrowserWindow, session, protocol, dialog } = require("electron"); |
||||
const path = require("path"); |
const path = require("path"); |
||||
const fs = require("fs"); |
const fs = require("fs"); |
||||
const logger = require("../utils/logger"); |
|
||||
|
|
||||
// Check if running in dev mode
|
// Global window reference
|
||||
const isDev = process.argv.includes("--inspect"); |
let mainWindow = null; |
||||
|
|
||||
|
// Debug flags
|
||||
|
const isDev = !app.isPackaged; |
||||
|
|
||||
|
// Helper for logging
|
||||
|
function logDebug(...args) { |
||||
|
// eslint-disable-next-line no-console
|
||||
|
console.log("[DEBUG]", ...args); |
||||
|
} |
||||
|
|
||||
|
function logError(...args) { |
||||
|
// eslint-disable-next-line no-console
|
||||
|
console.error("[ERROR]", ...args); |
||||
|
if (!isDev && mainWindow) { |
||||
|
dialog.showErrorBox("TimeSafari Error", args.join(" ")); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// Get the most appropriate app path
|
||||
|
function getAppPath() { |
||||
|
if (app.isPackaged) { |
||||
|
const possiblePaths = [ |
||||
|
path.join(process.resourcesPath, "app.asar", "dist-electron"), |
||||
|
path.join(process.resourcesPath, "app.asar"), |
||||
|
path.join(process.resourcesPath, "app"), |
||||
|
app.getAppPath(), |
||||
|
]; |
||||
|
|
||||
|
for (const testPath of possiblePaths) { |
||||
|
const testFile = path.join(testPath, "www", "index.html"); |
||||
|
if (fs.existsSync(testFile)) { |
||||
|
logDebug(`Found valid app path: ${testPath}`); |
||||
|
return testPath; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
logError("Could not find valid app path"); |
||||
|
return path.join(process.resourcesPath, "app.asar"); // Default fallback
|
||||
|
} else { |
||||
|
return __dirname; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// Create the browser window
|
||||
function createWindow() { |
function createWindow() { |
||||
// Add before createWindow function
|
logDebug("Creating window with paths:"); |
||||
const preloadPath = path.join(__dirname, "preload.js"); |
logDebug("- process.resourcesPath:", process.resourcesPath); |
||||
logger.log("Checking preload path:", preloadPath); |
logDebug("- app.getAppPath():", app.getAppPath()); |
||||
logger.log("Preload exists:", fs.existsSync(preloadPath)); |
logDebug("- __dirname:", __dirname); |
||||
|
|
||||
// Create the browser window.
|
// Create the browser window
|
||||
const mainWindow = new BrowserWindow({ |
mainWindow = new BrowserWindow({ |
||||
width: 1200, |
width: 1200, |
||||
height: 800, |
height: 800, |
||||
webPreferences: { |
webPreferences: { |
||||
nodeIntegration: false, |
preload: path.join(__dirname, "preload.js"), |
||||
contextIsolation: true, |
contextIsolation: true, |
||||
|
nodeIntegration: false, |
||||
webSecurity: true, |
webSecurity: true, |
||||
allowRunningInsecureContent: false, |
|
||||
preload: path.join(__dirname, "preload.js"), |
|
||||
}, |
}, |
||||
}); |
}); |
||||
|
|
||||
// Always open DevTools for now
|
// Fix root file paths - replaces all protocol handling
|
||||
mainWindow.webContents.openDevTools(); |
protocol.interceptFileProtocol("file", (request, callback) => { |
||||
|
let urlPath = request.url.substr(7); // Remove 'file://' prefix
|
||||
// Intercept requests to fix asset paths
|
urlPath = decodeURIComponent(urlPath); // Handle special characters
|
||||
mainWindow.webContents.session.webRequest.onBeforeRequest( |
|
||||
{ |
// Debug all asset requests
|
||||
urls: [ |
if ( |
||||
"file://*/*/assets/*", |
urlPath.includes("assets/") || |
||||
"file://*/assets/*", |
urlPath.endsWith(".js") || |
||||
"file:///assets/*", // Catch absolute paths
|
urlPath.endsWith(".css") || |
||||
"<all_urls>", // Catch all URLs as a fallback
|
urlPath.endsWith(".html") |
||||
], |
) { |
||||
}, |
logDebug(`Intercepted request for: ${urlPath}`); |
||||
(details, callback) => { |
} |
||||
let url = details.url; |
|
||||
|
// Fix paths for files at root like registerSW.js or manifest.webmanifest
|
||||
// Handle paths that don't start with file://
|
if ( |
||||
if (!url.startsWith("file://") && url.includes("/assets/")) { |
urlPath.endsWith("registerSW.js") || |
||||
url = `file://${path.join(__dirname, "www", url)}`; |
urlPath.endsWith("manifest.webmanifest") || |
||||
|
urlPath.endsWith("sw.js") |
||||
|
) { |
||||
|
const appBasePath = getAppPath(); |
||||
|
const filePath = path.join(appBasePath, "www", path.basename(urlPath)); |
||||
|
|
||||
|
if (fs.existsSync(filePath)) { |
||||
|
logDebug(`Serving ${urlPath} from ${filePath}`); |
||||
|
return callback({ path: filePath }); |
||||
|
} else { |
||||
|
// For service worker, provide empty content to avoid errors
|
||||
|
if (urlPath.endsWith("registerSW.js") || urlPath.endsWith("sw.js")) { |
||||
|
logDebug(`Providing empty SW file for ${urlPath}`); |
||||
|
// Create an empty JS file content that does nothing
|
||||
|
const tempFile = path.join( |
||||
|
app.getPath("temp"), |
||||
|
path.basename(urlPath), |
||||
|
); |
||||
|
fs.writeFileSync( |
||||
|
tempFile, |
||||
|
"// Service workers disabled in Electron\n", |
||||
|
); |
||||
|
return callback({ path: tempFile }); |
||||
|
} |
||||
} |
} |
||||
|
} |
||||
// Handle absolute paths starting with /assets/
|
|
||||
if (url.includes("/assets/") && !url.includes("/www/assets/")) { |
// Handle assets paths that might be requested from root
|
||||
const baseDir = url.includes("dist-electron") |
if (urlPath.startsWith("/assets/") || urlPath === "/assets") { |
||||
? url.substring( |
const appBasePath = getAppPath(); |
||||
0, |
const filePath = path.join(appBasePath, "www", urlPath); |
||||
url.indexOf("/dist-electron") + "/dist-electron".length, |
logDebug(`Redirecting ${urlPath} to ${filePath}`); |
||||
) |
return callback({ path: filePath }); |
||||
: `file://${__dirname}`; |
} |
||||
const assetPath = url.split("/assets/")[1]; |
|
||||
const newUrl = `${baseDir}/www/assets/${assetPath}`; |
// Handle assets paths that are missing the www folder
|
||||
callback({ redirectURL: newUrl }); |
if (urlPath.includes("/assets/")) { |
||||
return; |
const appBasePath = getAppPath(); |
||||
|
const relativePath = urlPath.substring(urlPath.indexOf("/assets/")); |
||||
|
const filePath = path.join(appBasePath, "www", relativePath); |
||||
|
if (fs.existsSync(filePath)) { |
||||
|
logDebug(`Fixing asset path ${urlPath} to ${filePath}`); |
||||
|
return callback({ path: filePath }); |
||||
} |
} |
||||
|
} |
||||
|
|
||||
callback({}); // No redirect for other URLs
|
// For all other paths, just pass them through
|
||||
}, |
callback({ path: urlPath }); |
||||
); |
}); |
||||
|
|
||||
if (isDev) { |
|
||||
// Debug info
|
|
||||
logger.log("Debug Info:"); |
|
||||
logger.log("Running in dev mode:", isDev); |
|
||||
logger.log("App is packaged:", app.isPackaged); |
|
||||
logger.log("Process resource path:", process.resourcesPath); |
|
||||
logger.log("App path:", app.getAppPath()); |
|
||||
logger.log("__dirname:", __dirname); |
|
||||
logger.log("process.cwd():", process.cwd()); |
|
||||
} |
|
||||
|
|
||||
const indexPath = path.join(__dirname, "www", "index.html"); |
|
||||
|
|
||||
if (isDev) { |
|
||||
logger.log("Loading index from:", indexPath); |
|
||||
logger.log("www path:", path.join(__dirname, "www")); |
|
||||
logger.log("www assets path:", path.join(__dirname, "www", "assets")); |
|
||||
} |
|
||||
|
|
||||
if (!fs.existsSync(indexPath)) { |
|
||||
logger.error(`Index file not found at: ${indexPath}`); |
|
||||
throw new Error("Index file not found"); |
|
||||
} |
|
||||
|
|
||||
// Add CSP headers to allow API connections
|
// Set up CSP headers - more permissive in dev mode
|
||||
mainWindow.webContents.session.webRequest.onHeadersReceived( |
session.defaultSession.webRequest.onHeadersReceived((details, callback) => { |
||||
(details, callback) => { |
callback({ |
||||
callback({ |
responseHeaders: { |
||||
responseHeaders: { |
...details.responseHeaders, |
||||
...details.responseHeaders, |
"Content-Security-Policy": [ |
||||
"Content-Security-Policy": [ |
isDev |
||||
"default-src 'self';" + |
? "default-src 'self' file:; script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline' https://fonts.googleapis.com; font-src 'self' https://fonts.gstatic.com; img-src 'self' data: blob: https://*; connect-src 'self' https://*" |
||||
"connect-src 'self' https://api.endorser.ch https://*.timesafari.app;" + |
: "default-src 'self' file:; script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline' https://fonts.googleapis.com; font-src 'self' https://fonts.gstatic.com; img-src 'self' data: blob: https://image.timesafari.app https://*.americancloud.com; connect-src 'self' https://api.timesafari.app https://api.endorser.ch https://test-api.endorser.ch https://fonts.googleapis.com", |
||||
"img-src 'self' data: https: blob:;" + |
], |
||||
"script-src 'self' 'unsafe-inline' 'unsafe-eval';" + |
}, |
||||
"style-src 'self' 'unsafe-inline';" + |
|
||||
"font-src 'self' data:;", |
|
||||
], |
|
||||
}, |
|
||||
}); |
|
||||
}, |
|
||||
); |
|
||||
|
|
||||
// Load the index.html
|
|
||||
mainWindow |
|
||||
.loadFile(indexPath) |
|
||||
.then(() => { |
|
||||
logger.log("Successfully loaded index.html"); |
|
||||
if (isDev) { |
|
||||
mainWindow.webContents.openDevTools(); |
|
||||
logger.log("DevTools opened - running in dev mode"); |
|
||||
} |
|
||||
}) |
|
||||
.catch((err) => { |
|
||||
logger.error("Failed to load index.html:", err); |
|
||||
logger.error("Attempted path:", indexPath); |
|
||||
}); |
}); |
||||
|
|
||||
// Listen for console messages from the renderer
|
|
||||
mainWindow.webContents.on("console-message", (_event, level, message) => { |
|
||||
logger.log("Renderer Console:", message); |
|
||||
}); |
}); |
||||
|
|
||||
// Add right after creating the BrowserWindow
|
// Load the index.html with modifications
|
||||
mainWindow.webContents.on( |
try { |
||||
"did-fail-load", |
const appPath = getAppPath(); |
||||
(event, errorCode, errorDescription) => { |
const wwwFolder = path.join(appPath, "www"); |
||||
logger.error("Page failed to load:", errorCode, errorDescription); |
const indexPath = path.join(wwwFolder, "index.html"); |
||||
}, |
|
||||
); |
logDebug("Loading app from:", indexPath); |
||||
|
|
||||
mainWindow.webContents.on("preload-error", (event, preloadPath, error) => { |
// Check if the file exists
|
||||
logger.error("Preload script error:", preloadPath, error); |
if (fs.existsSync(indexPath)) { |
||||
}); |
// Read and modify index.html to disable service worker
|
||||
|
let indexContent = fs.readFileSync(indexPath, "utf8"); |
||||
|
|
||||
|
// 1. Add base tag for proper path resolution
|
||||
|
indexContent = indexContent.replace( |
||||
|
"<head>", |
||||
|
`<head>\n <base href="file://${wwwFolder}/">`, |
||||
|
); |
||||
|
|
||||
|
// 2. Disable service worker registration by replacing the script
|
||||
|
if (indexContent.includes("registerSW.js")) { |
||||
|
indexContent = indexContent.replace( |
||||
|
/<script src="registerSW\.js"><\/script>/, |
||||
|
"<script>/* Service worker disabled in Electron */</script>", |
||||
|
); |
||||
|
} |
||||
|
|
||||
mainWindow.webContents.on( |
// Create a temp file with modified content
|
||||
"console-message", |
const tempDir = app.getPath("temp"); |
||||
(event, level, message, line, sourceId) => { |
const tempIndexPath = path.join(tempDir, "timesafari-index.html"); |
||||
logger.log("Renderer Console:", line, sourceId, message); |
fs.writeFileSync(tempIndexPath, indexContent); |
||||
}, |
|
||||
); |
// Load the modified index.html
|
||||
|
mainWindow.loadFile(tempIndexPath).catch((err) => { |
||||
|
logError("Failed to load via loadFile:", err); |
||||
|
|
||||
|
// Fallback to direct URL loading
|
||||
|
mainWindow.loadURL(`file://${tempIndexPath}`).catch((err2) => { |
||||
|
logError("Both loading methods failed:", err2); |
||||
|
mainWindow.loadURL( |
||||
|
"data:text/html,<h1>Error: Failed to load TimeSafari</h1><p>Please contact support.</p>", |
||||
|
); |
||||
|
}); |
||||
|
}); |
||||
|
} else { |
||||
|
logError(`Index file not found at: ${indexPath}`); |
||||
|
mainWindow.loadURL( |
||||
|
"data:text/html,<h1>Error: Cannot find application</h1><p>index.html not found</p>", |
||||
|
); |
||||
|
} |
||||
|
} catch (err) { |
||||
|
logError("Failed to load app:", err); |
||||
|
} |
||||
|
|
||||
// Enable remote debugging when in dev mode
|
// Open DevTools in development
|
||||
if (isDev) { |
if (isDev) { |
||||
mainWindow.webContents.openDevTools(); |
mainWindow.webContents.openDevTools(); |
||||
} |
} |
||||
|
|
||||
|
mainWindow.on("closed", () => { |
||||
|
mainWindow = null; |
||||
|
}); |
||||
} |
} |
||||
|
|
||||
// Handle app ready
|
// App lifecycle events
|
||||
app.whenReady().then(createWindow); |
app.whenReady().then(() => { |
||||
|
logDebug(`Starting TimeSafari v${app.getVersion()}`); |
||||
|
|
||||
// Handle all windows closed
|
// Skip the service worker registration for file:// protocol
|
||||
app.on("window-all-closed", () => { |
process.env.ELECTRON_DISABLE_SECURITY_WARNINGS = "true"; |
||||
if (process.platform !== "darwin") { |
|
||||
app.quit(); |
createWindow(); |
||||
} |
|
||||
|
app.on("activate", () => { |
||||
|
if (BrowserWindow.getAllWindows().length === 0) createWindow(); |
||||
|
}); |
||||
}); |
}); |
||||
|
|
||||
app.on("activate", () => { |
app.on("window-all-closed", () => { |
||||
if (BrowserWindow.getAllWindows().length === 0) { |
if (process.platform !== "darwin") app.quit(); |
||||
createWindow(); |
|
||||
} |
|
||||
}); |
}); |
||||
|
|
||||
// Handle any errors
|
// Handle uncaught exceptions
|
||||
process.on("uncaughtException", (error) => { |
process.on("uncaughtException", (error) => { |
||||
logger.error("Uncaught Exception:", error); |
logError("Uncaught Exception:", error); |
||||
}); |
}); |
||||
|
@ -1,78 +1,95 @@ |
|||||
const { contextBridge, ipcRenderer } = require("electron"); |
const { contextBridge, ipcRenderer } = require("electron"); |
||||
|
|
||||
const logger = { |
// Safety wrapper for logging
|
||||
log: (message, ...args) => { |
function safeLog(message) { |
||||
if (process.env.NODE_ENV !== "production") { |
try { |
||||
/* eslint-disable no-console */ |
// eslint-disable-next-line no-console
|
||||
console.log(message, ...args); |
console.log("[Preload]", message); |
||||
/* eslint-enable no-console */ |
} catch (e) { |
||||
} |
// Silent fail for logging
|
||||
}, |
|
||||
warn: (message, ...args) => { |
|
||||
if (process.env.NODE_ENV !== "production") { |
|
||||
/* eslint-disable no-console */ |
|
||||
console.warn(message, ...args); |
|
||||
/* eslint-enable no-console */ |
|
||||
} |
|
||||
}, |
|
||||
error: (message, ...args) => { |
|
||||
/* eslint-disable no-console */ |
|
||||
console.error(message, ...args); // Errors should always be logged
|
|
||||
/* eslint-enable no-console */ |
|
||||
}, |
|
||||
}; |
|
||||
|
|
||||
// Use a more direct path resolution approach
|
|
||||
const getPath = (pathType) => { |
|
||||
switch (pathType) { |
|
||||
case "userData": |
|
||||
return ( |
|
||||
process.env.APPDATA || |
|
||||
(process.platform === "darwin" |
|
||||
? `${process.env.HOME}/Library/Application Support` |
|
||||
: `${process.env.HOME}/.local/share`) |
|
||||
); |
|
||||
case "home": |
|
||||
return process.env.HOME; |
|
||||
case "appPath": |
|
||||
return process.resourcesPath; |
|
||||
default: |
|
||||
return ""; |
|
||||
} |
} |
||||
}; |
} |
||||
|
|
||||
logger.log("Preload script starting..."); |
// Initialize
|
||||
|
safeLog("Preload script starting..."); |
||||
|
|
||||
try { |
try { |
||||
|
// Mock service worker registration to prevent errors
|
||||
|
if (window.navigator) { |
||||
|
// Override the service worker registration to return a fake promise that resolves with nothing
|
||||
|
window.navigator.serviceWorker = { |
||||
|
register: () => Promise.resolve({}), |
||||
|
getRegistration: () => Promise.resolve(null), |
||||
|
ready: Promise.resolve({}), |
||||
|
}; |
||||
|
} |
||||
|
|
||||
|
// Safely expose specific APIs to the renderer process
|
||||
contextBridge.exposeInMainWorld("electronAPI", { |
contextBridge.exposeInMainWorld("electronAPI", { |
||||
// Path utilities
|
// Basic flags/info
|
||||
getPath, |
isElectron: true, |
||||
|
|
||||
|
// Disable service worker in Electron
|
||||
|
disableServiceWorker: true, |
||||
|
|
||||
|
// Logging
|
||||
|
log: (message) => { |
||||
|
try { |
||||
|
// eslint-disable-next-line no-console
|
||||
|
console.log("[Renderer]", message); |
||||
|
} catch (e) { |
||||
|
// Silence any errors from logging
|
||||
|
} |
||||
|
}, |
||||
|
|
||||
|
// Report errors to main process
|
||||
|
reportError: (error) => { |
||||
|
try { |
||||
|
ipcRenderer.send("app-error", error.toString()); |
||||
|
} catch (e) { |
||||
|
// eslint-disable-next-line no-console
|
||||
|
console.error("Failed to report error to main process", e); |
||||
|
} |
||||
|
}, |
||||
|
|
||||
|
// Safe path handling helper (no Node modules needed)
|
||||
|
joinPath: (...parts) => { |
||||
|
return parts.join("/").replace(/\/\//g, "/"); |
||||
|
}, |
||||
|
|
||||
|
// Fix asset URLs
|
||||
|
resolveAssetUrl: (assetPath) => { |
||||
|
if (assetPath.startsWith("/assets/")) { |
||||
|
return assetPath; // Already properly formed
|
||||
|
} |
||||
|
if (assetPath.startsWith("assets/")) { |
||||
|
return "/" + assetPath; // Add leading slash
|
||||
|
} |
||||
|
return assetPath; |
||||
|
}, |
||||
|
|
||||
// IPC functions
|
// Send messages to main process
|
||||
send: (channel, data) => { |
send: (channel, data) => { |
||||
const validChannels = ["toMain"]; |
// Whitelist channels for security
|
||||
|
const validChannels = ["app-event", "log-event", "app-error"]; |
||||
if (validChannels.includes(channel)) { |
if (validChannels.includes(channel)) { |
||||
ipcRenderer.send(channel, data); |
ipcRenderer.send(channel, data); |
||||
} |
} |
||||
}, |
}, |
||||
|
|
||||
|
// Receive messages from main process
|
||||
receive: (channel, func) => { |
receive: (channel, func) => { |
||||
const validChannels = ["fromMain"]; |
const validChannels = ["app-notification", "log-response"]; |
||||
if (validChannels.includes(channel)) { |
if (validChannels.includes(channel)) { |
||||
ipcRenderer.on(channel, (event, ...args) => func(...args)); |
// Remove old listeners to avoid memory leaks
|
||||
|
ipcRenderer.removeAllListeners(channel); |
||||
|
// Add the new listener
|
||||
|
ipcRenderer.on(channel, (_, ...args) => func(...args)); |
||||
} |
} |
||||
}, |
}, |
||||
// Environment info
|
|
||||
env: { |
|
||||
isElectron: true, |
|
||||
isDev: process.env.NODE_ENV === "development", |
|
||||
}, |
|
||||
// Path utilities
|
|
||||
getBasePath: () => { |
|
||||
return process.env.NODE_ENV === "development" ? "/" : "./"; |
|
||||
}, |
|
||||
}); |
}); |
||||
|
|
||||
logger.log("Preload script completed successfully"); |
safeLog("Preload script completed successfully"); |
||||
} catch (error) { |
} catch (err) { |
||||
logger.error("Error in preload script:", error); |
safeLog("Error in preload script: " + err.toString()); |
||||
} |
} |
||||
|
Loading…
Reference in new issue