You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 

236 lines
7.7 KiB

const { app, BrowserWindow, session, protocol, dialog } = require("electron");
const path = require("path");
const fs = require("fs");
// Global window reference
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() {
logDebug("Creating window with paths:");
logDebug("- process.resourcesPath:", process.resourcesPath);
logDebug("- app.getAppPath():", app.getAppPath());
logDebug("- __dirname:", __dirname);
// Create the browser window
mainWindow = new BrowserWindow({
width: 1200,
height: 800,
webPreferences: {
preload: path.join(__dirname, "preload.js"),
contextIsolation: true,
nodeIntegration: false,
webSecurity: true,
},
});
// Fix root file paths - replaces all protocol handling
protocol.interceptFileProtocol("file", (request, callback) => {
let urlPath = request.url.substr(7); // Remove 'file://' prefix
urlPath = decodeURIComponent(urlPath); // Handle special characters
// Debug all asset requests
if (
urlPath.includes("assets/") ||
urlPath.endsWith(".js") ||
urlPath.endsWith(".css") ||
urlPath.endsWith(".html")
) {
logDebug(`Intercepted request for: ${urlPath}`);
}
// Fix paths for files at root like registerSW.js or manifest.webmanifest
if (
urlPath.endsWith("registerSW.js") ||
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 assets paths that might be requested from root
if (urlPath.startsWith("/assets/") || urlPath === "/assets") {
const appBasePath = getAppPath();
const filePath = path.join(appBasePath, "www", urlPath);
logDebug(`Redirecting ${urlPath} to ${filePath}`);
return callback({ path: filePath });
}
// Handle assets paths that are missing the www folder
if (urlPath.includes("/assets/")) {
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 });
}
}
// For all other paths, just pass them through
callback({ path: urlPath });
});
// Set up CSP headers - more permissive in dev mode
session.defaultSession.webRequest.onHeadersReceived((details, callback) => {
callback({
responseHeaders: {
...details.responseHeaders,
"Content-Security-Policy": [
isDev
? "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://*"
: "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",
],
},
});
});
// Load the index.html with modifications
try {
const appPath = getAppPath();
const wwwFolder = path.join(appPath, "www");
const indexPath = path.join(wwwFolder, "index.html");
logDebug("Loading app from:", indexPath);
// Check if the file exists
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>",
);
}
// Create a temp file with modified content
const tempDir = app.getPath("temp");
const tempIndexPath = path.join(tempDir, "timesafari-index.html");
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);
}
// Open DevTools in development
if (isDev) {
mainWindow.webContents.openDevTools();
}
mainWindow.on("closed", () => {
mainWindow = null;
});
}
// App lifecycle events
app.whenReady().then(() => {
logDebug(`Starting TimeSafari v${app.getVersion()}`);
// Skip the service worker registration for file:// protocol
process.env.ELECTRON_DISABLE_SECURITY_WARNINGS = "true";
createWindow();
app.on("activate", () => {
if (BrowserWindow.getAllWindows().length === 0) createWindow();
});
});
app.on("window-all-closed", () => {
if (process.platform !== "darwin") app.quit();
});
// Handle uncaught exceptions
process.on("uncaughtException", (error) => {
logError("Uncaught Exception:", error);
});