WIP (fix): improve electron build configuration and service worker handling

- Properly disable service workers in electron builds
- Add CSP headers for electron security
- Fix path resolution in electron context
- Improve preload script error handling and IPC setup
- Update build scripts for better electron/capacitor compatibility
- Fix router path handling in electron context
- Remove electron-builder dependency
- Streamline build process and output structure

This change improves the stability and security of electron builds while
maintaining PWA functionality in web builds. Service workers are now
properly disabled in electron context, and path resolution issues are
fixed.
This commit is contained in:
Matthew Raymer
2025-02-12 13:17:25 +00:00
parent 1be5c530c5
commit d8c1a84cfe
10 changed files with 310 additions and 2735 deletions

View File

@@ -2,99 +2,103 @@ const { app, BrowserWindow } = require("electron");
const path = require("path");
const fs = require("fs");
// Check if running in dev mode
const isDev = process.argv.includes('--inspect');
function createWindow() {
// Add before createWindow function
const preloadPath = path.join(__dirname, 'preload.js');
console.log('Checking preload path:', preloadPath);
console.log('Preload exists:', fs.existsSync(preloadPath));
// Create the browser window.
const mainWindow = new BrowserWindow({
width: 1200,
height: 800,
webPreferences: {
nodeIntegration: true,
contextIsolation: false,
nodeIntegration: false,
contextIsolation: true,
webSecurity: true,
allowRunningInsecureContent: false,
preload: path.join(__dirname, 'preload.js')
},
});
// Disable service worker in Electron
mainWindow.webContents.session.setPermissionRequestHandler(
(webContents, permission, callback) => {
if (permission === "serviceWorker") {
return callback(false);
}
callback(true);
},
);
// Get the correct app path for packaged and development environments
const appPath = app.isPackaged ? process.resourcesPath : app.getAppPath();
// Add debug logging for paths
// Debug info
console.log("Debug Info:");
console.log("Running in dev mode:", isDev);
console.log("App is packaged:", app.isPackaged);
console.log("Process resource path:", process.resourcesPath);
console.log("App path:", appPath);
console.log("App path:", app.getAppPath());
console.log("__dirname:", __dirname);
console.log("process.cwd():", process.cwd());
console.log("www path:", path.join(process.resourcesPath, "www"));
console.log(
"www assets path:",
path.join(process.resourcesPath, "www", "assets"),
);
// Try both possible www locations
const possiblePaths = [
path.join(appPath, "www", "index.html"),
path.join(appPath, "..", "www", "index.html"),
path.join(process.resourcesPath, "www", "index.html"),
];
const indexPath = path.join(__dirname, 'www', 'index.html');
console.log("www path:", path.join(__dirname, 'www'));
console.log("www assets path:", path.join(__dirname, 'www', 'assets'));
let indexPath;
for (const testPath of possiblePaths) {
console.log("Testing path:", testPath);
if (fs.existsSync(testPath)) {
indexPath = testPath;
console.log("Found valid path:", indexPath);
break;
}
if (!fs.existsSync(indexPath)) {
console.error(`Index file not found at: ${indexPath}`);
throw new Error('Index file not found');
}
// Set CSP headers
mainWindow.webContents.session.webRequest.onHeadersReceived((details, callback) => {
callback({
responseHeaders: {
...details.responseHeaders,
'Content-Security-Policy': [
"default-src 'self';" +
"style-src 'self' 'unsafe-inline' https://fonts.googleapis.com;" +
"font-src 'self' https://fonts.gstatic.com;" +
"script-src 'self' 'unsafe-eval' 'unsafe-inline';" +
"img-src 'self' data: https:;"
]
}
});
});
// Load the index.html
mainWindow
.loadFile(indexPath)
.then(() => {
console.log("Successfully loaded index.html");
// Always open DevTools in packaged app for debugging
mainWindow.webContents.openDevTools();
if (isDev) {
mainWindow.webContents.openDevTools();
console.log("DevTools opened - running in dev mode");
}
})
.catch((err) => {
console.error("Failed to load index.html:", err);
console.error("Attempted path:", indexPath);
});
// Listen for page errors
mainWindow.webContents.on(
"did-fail-load",
(event, errorCode, errorDescription) => {
console.error("Page failed to load:", errorCode, errorDescription);
},
);
// Listen for console messages from the renderer
mainWindow.webContents.on("console-message", (_event, level, message) => {
console.log("Renderer Console:", message);
});
// Add right after creating the BrowserWindow
mainWindow.webContents.on('did-fail-load', (event, errorCode, errorDescription) => {
console.error('Page failed to load:', errorCode, errorDescription);
});
mainWindow.webContents.on('preload-error', (event, preloadPath, error) => {
console.error('Preload script error:', preloadPath, error);
});
mainWindow.webContents.on('console-message', (event, level, message, line, sourceId) => {
console.log('Renderer Console:', message);
});
// Enable remote debugging when in dev mode
if (isDev) {
mainWindow.webContents.openDevTools();
}
}
// Handle app ready
app.whenReady().then(() => {
createWindow();
app.on("activate", () => {
if (BrowserWindow.getAllWindows().length === 0) {
createWindow();
}
});
});
app.whenReady().then(createWindow);
// Handle all windows closed
app.on("window-all-closed", () => {
@@ -103,6 +107,12 @@ app.on("window-all-closed", () => {
}
});
app.on("activate", () => {
if (BrowserWindow.getAllWindows().length === 0) {
createWindow();
}
});
// Handle any errors
process.on("uncaughtException", (error) => {
console.error("Uncaught Exception:", error);

View File

@@ -1,5 +1,55 @@
const { contextBridge } = require("electron");
const { contextBridge, ipcRenderer } = require('electron');
contextBridge.exposeInMainWorld("api", {
logMessage: (message) => console.log(`[Electron]: ${message}`),
});
// 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 '';
}
};
console.log('Preload script starting...');
try {
contextBridge.exposeInMainWorld('electronAPI', {
// Path utilities
getPath,
// IPC functions
send: (channel, data) => {
const validChannels = ['toMain'];
if (validChannels.includes(channel)) {
ipcRenderer.send(channel, data);
}
},
receive: (channel, func) => {
const validChannels = ['fromMain'];
if (validChannels.includes(channel)) {
ipcRenderer.on(channel, (event, ...args) => func(...args));
}
},
// Environment info
env: {
isElectron: true,
isDev: process.env.NODE_ENV === 'development'
},
// Path utilities
getBasePath: () => {
return process.env.NODE_ENV === 'development' ? '/' : './';
}
});
console.log('Preload script completed successfully');
} catch (error) {
console.error('Error in preload script:', error);
}

View File

@@ -2,14 +2,11 @@
import { register } from "register-service-worker";
// NODE_ENV is "production" by default with "vite build". See https://vitejs.dev/guide/env-and-mode
if (import.meta.env.NODE_ENV === "production") {
register("/sw_scripts-combined.js", {
// Only register service worker if explicitly enabled and in production
if (process.env.VITE_PWA_ENABLED === 'true' && process.env.NODE_ENV === "production") {
register(`${process.env.BASE_URL}sw.js`, {
ready() {
console.log(
"App is being served from cache by a service worker.\n" +
"For more details, visit https://goo.gl/AFskqB",
);
console.log("Service worker is active.");
},
registered() {
console.log("Service worker has been registered.");
@@ -24,12 +21,12 @@ if (import.meta.env.NODE_ENV === "production") {
console.log("New content is available; please refresh.");
},
offline() {
console.log(
"No internet connection found. App is running in offline mode.",
);
console.log("No internet connection found. App is running in offline mode.");
},
error(error) {
console.error("Error during service worker registration:", error);
},
}
});
} else {
console.log("Service worker registration skipped - not enabled or not in production");
}

View File

@@ -284,9 +284,9 @@ const routes: Array<RouteRecordRaw> = [
},
];
const isElectron = window.location.protocol === "file:"; // Check if running in Electron
const isElectron = window.location.protocol === "file:";
const initialPath = isElectron
? window.location.pathname.replace("/dist-electron/index.html", "/")
? window.location.pathname.split('/dist-electron/www/')[1] || '/'
: window.location.pathname;
const history = isElectron

View File

@@ -1,16 +1,14 @@
import * as path from "path";
import { promises as fs } from "fs";
import { fileURLToPath } from 'url';
export async function loadAppConfig() {
const packageJson = await loadPackageJson();
const appName = process.env.TIME_SAFARI_APP_TITLE || packageJson.name;
const __dirname = path.dirname(fileURLToPath(import.meta.url));
return {
pwaConfig: {
registerType: "autoUpdate",
strategies: "injectManifest",
srcDir: ".",
filename: "sw_scripts-combined.js",
manifest: {
name: appName,
short_name: appName,
@@ -36,34 +34,21 @@ export async function loadAppConfig() {
sizes: "512x512",
type: "image/png",
purpose: "maskable",
},
],
share_target: {
action: "/share-target",
method: "POST",
enctype: "multipart/form-data",
params: {
files: [
{
name: "photo",
accept: ["image/*"],
},
],
},
},
},
}
]
}
},
aliasConfig: {
"@": path.resolve(__dirname, "./src"),
buffer: path.resolve(__dirname, "node_modules", "buffer"),
"dexie-export-import/dist/import":
"dexie-export-import/dist/import/index.js",
},
"@": path.resolve(path.dirname(__dirname), "src"),
buffer: path.resolve(path.dirname(__dirname), "node_modules", "buffer"),
"dexie-export-import/dist/import": "dexie-export-import/dist/import/index.js",
}
};
}
async function loadPackageJson() {
const packageJsonPath = path.resolve(__dirname, "package.json");
const __dirname = path.dirname(fileURLToPath(import.meta.url));
const packageJsonPath = path.resolve(path.dirname(__dirname), "package.json");
const packageJsonData = await fs.readFile(packageJsonPath, "utf-8");
return JSON.parse(packageJsonData);
}