Browse Source
- Add electron platform section to capacitor.config.json - Configure deep linking with timesafari:// scheme - Set up build options for macOS, Windows, and Linux - Configure output directory and file inclusion - Add platform-specific build targets (DMG, NSIS, AppImage) - Support both x64 and arm64 architectures for macOS - Set appropriate app categories for each platform This enables building TimeSafari as a native desktop application using Capacitor's Electron platform while maintaining existing mobile and web functionality.streamline-attempt
29 changed files with 418 additions and 1985 deletions
@ -1,15 +0,0 @@ |
|||
VM4 sandbox_bundle:2 Unable to load preload script: /home/noone/projects/timesafari/crowd-master/preload.js |
|||
(anonymous) @ VM4 sandbox_bundle:2 |
|||
VM4 sandbox_bundle:2 Error: ENOENT: no such file or directory, open '/home/noone/projects/timesafari/crowd-master/preload.js' |
|||
at async open (node:internal/fs/promises:639:25) |
|||
at async Object.readFile (node:internal/fs/promises:1246:14) |
|||
at async node:electron/js2c/browser_init:2:108714 |
|||
at async Promise.all (index 0) |
|||
at async node:electron/js2c/browser_init:2:108650 |
|||
at async IpcMainImpl.<anonymous> (node:electron/js2c/browser_init:2:105615) |
|||
(anonymous) @ VM4 sandbox_bundle:2 |
|||
main.electron.ts:1 |
|||
|
|||
|
|||
Failed to load resource: net::ERR_FILE_NOT_FOUND |
|||
index.html:1 Uncaught (in promise) TypeError: Failed to fetch dynamically imported module: file:///tmp/.mount_TimeSaGVOt4a/resources/app.asar/dist-electron/www/src/main.electron.ts |
@ -1,4 +0,0 @@ |
|||
main.electron.js:1 |
|||
|
|||
|
|||
Failed to load resource: net::ERR_FILE_NOT_FOUND |
@ -1,82 +0,0 @@ |
|||
#!/bin/bash |
|||
# build-electron-linux.sh |
|||
# Author: Matthew Raymer |
|||
# Description: Electron Linux build script for TimeSafari application |
|||
# This script builds Electron packages for Linux with support for different formats. |
|||
# |
|||
# Usage: ./scripts/build-electron-linux.sh [deb|prod] |
|||
# - No argument: Builds AppImage |
|||
# - deb: Builds .deb package |
|||
# - prod: Builds production AppImage |
|||
# |
|||
# Exit Codes: |
|||
# 1 - Build failed |
|||
# 2 - Invalid argument |
|||
|
|||
# Exit on any error |
|||
set -e |
|||
|
|||
# Source common utilities |
|||
source "$(dirname "$0")/common.sh" |
|||
|
|||
# Parse command line arguments |
|||
parse_args "$@" |
|||
|
|||
# Parse build type argument |
|||
BUILD_TYPE=${1:-"appimage"} |
|||
PRODUCTION=false |
|||
|
|||
case $BUILD_TYPE in |
|||
"deb") |
|||
BUILD_TARGET="deb" |
|||
log_info "Building .deb package" |
|||
;; |
|||
"prod") |
|||
BUILD_TARGET="AppImage" |
|||
PRODUCTION=true |
|||
log_info "Building production AppImage" |
|||
;; |
|||
"appimage"|"") |
|||
BUILD_TARGET="AppImage" |
|||
log_info "Building AppImage" |
|||
;; |
|||
*) |
|||
log_error "Invalid build type: $BUILD_TYPE" |
|||
log_error "Usage: $0 [deb|prod]" |
|||
exit 2 |
|||
;; |
|||
esac |
|||
|
|||
# Print build header |
|||
print_header "TimeSafari Electron Linux Build" |
|||
log_info "Starting Linux build process at $(date)" |
|||
log_info "Build type: $BUILD_TYPE" |
|||
log_info "Build target: $BUILD_TARGET" |
|||
log_info "Production mode: $PRODUCTION" |
|||
|
|||
# Setup environment for Electron build |
|||
setup_build_env "electron" "$PRODUCTION" |
|||
|
|||
# Setup application directories |
|||
setup_app_directories |
|||
|
|||
# Load environment from .env file if it exists |
|||
load_env_file ".env" |
|||
|
|||
# Step 1: Build Electron application |
|||
if [ "$PRODUCTION" = true ]; then |
|||
safe_execute "Building production Electron application" "npm run build:electron-prod" || exit 1 |
|||
else |
|||
safe_execute "Building Electron application" "npm run build:electron" || exit 1 |
|||
fi |
|||
|
|||
# Step 2: Build package |
|||
safe_execute "Building Linux package" "npx electron-builder --linux $BUILD_TARGET" || exit 1 |
|||
|
|||
# Print build summary |
|||
log_success "Linux build completed successfully!" |
|||
log_info "Package type: $BUILD_TARGET" |
|||
print_footer "Linux Build" |
|||
|
|||
# Exit with success |
|||
exit 0 |
@ -1,74 +0,0 @@ |
|||
#!/bin/bash |
|||
# build-electron-mac.sh |
|||
# Author: Matthew Raymer |
|||
# Description: Electron Mac build script for TimeSafari application |
|||
# This script builds Electron packages for macOS with support for universal builds. |
|||
# |
|||
# Usage: ./scripts/build-electron-mac.sh [universal] |
|||
# - No argument: Builds standard Mac package |
|||
# - universal: Builds universal Mac package (Intel + Apple Silicon) |
|||
# |
|||
# Exit Codes: |
|||
# 1 - Build failed |
|||
# 2 - Invalid argument |
|||
|
|||
# Exit on any error |
|||
set -e |
|||
|
|||
# Source common utilities |
|||
source "$(dirname "$0")/common.sh" |
|||
|
|||
# Parse command line arguments |
|||
parse_args "$@" |
|||
|
|||
# Parse build type argument |
|||
BUILD_TYPE=${1:-"standard"} |
|||
UNIVERSAL=false |
|||
|
|||
case $BUILD_TYPE in |
|||
"universal") |
|||
UNIVERSAL=true |
|||
log_info "Building universal Mac package (Intel + Apple Silicon)" |
|||
;; |
|||
"standard"|"") |
|||
log_info "Building standard Mac package" |
|||
;; |
|||
*) |
|||
log_error "Invalid build type: $BUILD_TYPE" |
|||
log_error "Usage: $0 [universal]" |
|||
exit 2 |
|||
;; |
|||
esac |
|||
|
|||
# Print build header |
|||
print_header "TimeSafari Electron Mac Build" |
|||
log_info "Starting Mac build process at $(date)" |
|||
log_info "Build type: $BUILD_TYPE" |
|||
log_info "Universal build: $UNIVERSAL" |
|||
|
|||
# Setup environment for Electron build (production mode for packaging) |
|||
setup_build_env "electron" "true" |
|||
|
|||
# Setup application directories |
|||
setup_app_directories |
|||
|
|||
# Load environment from .env file if it exists |
|||
load_env_file ".env" |
|||
|
|||
# Step 1: Build Electron application |
|||
safe_execute "Building Electron application" "npm run build:electron-prod" || exit 1 |
|||
|
|||
# Step 2: Build package |
|||
if [ "$UNIVERSAL" = true ]; then |
|||
safe_execute "Building universal Mac package" "npx electron-builder --mac --universal" || exit 1 |
|||
else |
|||
safe_execute "Building Mac package" "npx electron-builder --mac" || exit 1 |
|||
fi |
|||
|
|||
# Print build summary |
|||
log_success "Mac build completed successfully!" |
|||
log_info "Package type: $([ "$UNIVERSAL" = true ] && echo "Universal" || echo "Standard")" |
|||
print_footer "Mac Build" |
|||
|
|||
# Exit with success |
|||
exit 0 |
@ -1,148 +0,0 @@ |
|||
const fs = require('fs'); |
|||
const path = require('path'); |
|||
|
|||
console.log('Starting electron build process...'); |
|||
|
|||
// Define paths
|
|||
const electronDistPath = path.join(__dirname, '..', 'dist-electron'); |
|||
const wwwPath = path.join(electronDistPath, 'www'); |
|||
|
|||
// Create www directory if it doesn't exist
|
|||
if (!fs.existsSync(wwwPath)) { |
|||
fs.mkdirSync(wwwPath, { recursive: true }); |
|||
} |
|||
|
|||
// Copy the Vite-built index.html to www directory
|
|||
const viteIndexPath = path.join(electronDistPath, 'index.html'); |
|||
const wwwIndexPath = path.join(wwwPath, 'index.html'); |
|||
|
|||
if (fs.existsSync(viteIndexPath)) { |
|||
console.log('Copying Vite-built index.html to www directory...'); |
|||
fs.copyFileSync(viteIndexPath, wwwIndexPath); |
|||
|
|||
// Remove the original index.html from dist-electron root
|
|||
fs.unlinkSync(viteIndexPath); |
|||
console.log('Moved index.html to www directory'); |
|||
} else { |
|||
console.error('Vite-built index.html not found at:', viteIndexPath); |
|||
process.exit(1); |
|||
} |
|||
|
|||
// Copy assets directory if it exists in dist-electron
|
|||
const assetsSrc = path.join(electronDistPath, 'assets'); |
|||
const assetsDest = path.join(wwwPath, 'assets'); |
|||
|
|||
if (fs.existsSync(assetsSrc)) { |
|||
console.log('Moving assets directory to www...'); |
|||
if (fs.existsSync(assetsDest)) { |
|||
fs.rmSync(assetsDest, { recursive: true, force: true }); |
|||
} |
|||
fs.renameSync(assetsSrc, assetsDest); |
|||
console.log('Moved assets directory to www'); |
|||
} |
|||
|
|||
// Copy favicon if it exists
|
|||
const faviconSrc = path.join(electronDistPath, 'favicon.ico'); |
|||
const faviconDest = path.join(wwwPath, 'favicon.ico'); |
|||
|
|||
if (fs.existsSync(faviconSrc)) { |
|||
console.log('Moving favicon to www...'); |
|||
fs.renameSync(faviconSrc, faviconDest); |
|||
console.log('Moved favicon to www'); |
|||
} |
|||
|
|||
// Remove service worker files from www directory
|
|||
const swFilesToRemove = [ |
|||
'sw.js', |
|||
'sw.js.map', |
|||
'workbox-*.js', |
|||
'workbox-*.js.map', |
|||
'registerSW.js', |
|||
'manifest.webmanifest' |
|||
]; |
|||
|
|||
console.log('Removing service worker files...'); |
|||
swFilesToRemove.forEach(pattern => { |
|||
const files = fs.readdirSync(wwwPath).filter(file => |
|||
file.match(new RegExp(pattern.replace(/\*/g, '.*'))) |
|||
); |
|||
files.forEach(file => { |
|||
const filePath = path.join(wwwPath, file); |
|||
console.log(`Removing ${filePath}`); |
|||
try { |
|||
fs.unlinkSync(filePath); |
|||
} catch (err) { |
|||
console.warn(`Could not remove ${filePath}:`, err.message); |
|||
} |
|||
}); |
|||
}); |
|||
|
|||
// Also check and remove from assets directory
|
|||
if (fs.existsSync(assetsDest)) { |
|||
swFilesToRemove.forEach(pattern => { |
|||
const files = fs.readdirSync(assetsDest).filter(file => |
|||
file.match(new RegExp(pattern.replace(/\*/g, '.*'))) |
|||
); |
|||
files.forEach(file => { |
|||
const filePath = path.join(assetsDest, file); |
|||
console.log(`Removing ${filePath}`); |
|||
try { |
|||
fs.unlinkSync(filePath); |
|||
} catch (err) { |
|||
console.warn(`Could not remove ${filePath}:`, err.message); |
|||
} |
|||
}); |
|||
}); |
|||
} |
|||
|
|||
// Verify the final index.html structure
|
|||
const finalIndexContent = fs.readFileSync(wwwIndexPath, 'utf8'); |
|||
console.log('Final index.html structure:'); |
|||
console.log('- Has CSS link:', finalIndexContent.includes('<link rel="stylesheet"')); |
|||
console.log('- Has main script:', finalIndexContent.includes('main.electron.js')); |
|||
console.log('- No service worker references:', !finalIndexContent.includes('serviceWorker')); |
|||
|
|||
// Copy main process files to the correct location
|
|||
console.log('Setting up main process files...'); |
|||
|
|||
// The main process files are already in the correct location
|
|||
// Just verify they exist and are ready
|
|||
const mainPath = path.join(electronDistPath, 'main.js'); |
|||
const preloadPath = path.join(electronDistPath, 'preload.js'); |
|||
|
|||
if (fs.existsSync(mainPath)) { |
|||
console.log('Main process file ready at:', mainPath); |
|||
} else { |
|||
console.error('Main process file not found at:', mainPath); |
|||
process.exit(1); |
|||
} |
|||
|
|||
if (fs.existsSync(preloadPath)) { |
|||
console.log('Preload script ready at:', preloadPath); |
|||
} else { |
|||
console.warn('Preload script not found at:', preloadPath); |
|||
} |
|||
|
|||
// Clean up any remaining files in dist-electron root (except main.js, preload.js, and www directory)
|
|||
const remainingFiles = fs.readdirSync(electronDistPath); |
|||
remainingFiles.forEach(file => { |
|||
if (file !== 'main.js' && file !== 'preload.js' && file !== 'www') { |
|||
const filePath = path.join(electronDistPath, file); |
|||
console.log(`Removing remaining file: ${file}`); |
|||
try { |
|||
if (fs.statSync(filePath).isDirectory()) { |
|||
fs.rmSync(filePath, { recursive: true, force: true }); |
|||
} else { |
|||
fs.unlinkSync(filePath); |
|||
} |
|||
} catch (err) { |
|||
console.warn(`Could not remove ${filePath}:`, err.message); |
|||
} |
|||
} |
|||
}); |
|||
|
|||
console.log('Electron build process completed successfully'); |
|||
console.log('Final structure:'); |
|||
console.log('- Main process:', path.join(electronDistPath, 'main.js')); |
|||
console.log('- Preload script:', path.join(electronDistPath, 'preload.js')); |
|||
console.log('- Web assets:', path.join(electronDistPath, 'www')); |
@ -1,53 +0,0 @@ |
|||
#!/bin/bash |
|||
# build-electron.sh |
|||
# Author: Matthew Raymer |
|||
# Description: Electron build script for TimeSafari application |
|||
# This script handles the complete Electron build process including cleanup, |
|||
# TypeScript compilation, Vite build, and Electron-specific setup. |
|||
# |
|||
# Exit Codes: |
|||
# 1 - Cleanup failed |
|||
# 2 - TypeScript compilation failed |
|||
# 3 - Vite build failed |
|||
# 4 - Electron build script failed |
|||
|
|||
# Exit on any error |
|||
set -e |
|||
|
|||
# Source common utilities |
|||
source "$(dirname "$0")/common.sh" |
|||
|
|||
# Parse command line arguments |
|||
parse_args "$@" |
|||
|
|||
# Print build header |
|||
print_header "TimeSafari Electron Build Process" |
|||
log_info "Starting Electron build process at $(date)" |
|||
|
|||
# Setup environment for Electron build |
|||
setup_build_env "electron" |
|||
|
|||
# Setup application directories |
|||
setup_app_directories |
|||
|
|||
# Load environment from .env file if it exists |
|||
load_env_file ".env" |
|||
|
|||
# Step 1: Clean previous builds |
|||
safe_execute "Cleaning previous builds" "npm run clean:electron" || exit 1 |
|||
|
|||
# Step 2: Compile TypeScript for Electron |
|||
safe_execute "Compiling TypeScript for Electron" "npx tsc -p tsconfig.electron.json" || exit 2 |
|||
|
|||
# Step 3: Build with Vite |
|||
safe_execute "Building with Vite" "npx vite build --config vite.config.electron.mts" || exit 3 |
|||
|
|||
# Step 4: Run Electron build script |
|||
safe_execute "Running Electron build script" "node scripts/build-electron.js" || exit 4 |
|||
|
|||
# Print build summary |
|||
log_success "Electron build completed successfully!" |
|||
print_footer "Electron Build" |
|||
|
|||
# Exit with success |
|||
exit 0 |
@ -1,44 +0,0 @@ |
|||
#!/bin/bash |
|||
# electron-dev.sh |
|||
# Author: Matthew Raymer |
|||
# Description: Electron development script for TimeSafari application |
|||
# This script builds the application and starts Electron for development. |
|||
# |
|||
# Exit Codes: |
|||
# 1 - Build failed |
|||
# 2 - Electron start failed |
|||
|
|||
# Exit on any error |
|||
set -e |
|||
|
|||
# Source common utilities |
|||
source "$(dirname "$0")/common.sh" |
|||
|
|||
# Parse command line arguments |
|||
parse_args "$@" |
|||
|
|||
# Print dev header |
|||
print_header "TimeSafari Electron Development" |
|||
log_info "Starting Electron development at $(date)" |
|||
|
|||
# Setup environment for Electron development |
|||
setup_build_env "electron" |
|||
|
|||
# Setup application directories |
|||
setup_app_directories |
|||
|
|||
# Load environment from .env file if it exists |
|||
load_env_file ".env" |
|||
|
|||
# Step 1: Build the application |
|||
safe_execute "Building application" "npm run build" || exit 1 |
|||
|
|||
# Step 2: Start Electron |
|||
safe_execute "Starting Electron" "electron ." || exit 2 |
|||
|
|||
# Print dev summary |
|||
log_success "Electron development session ended" |
|||
print_footer "Electron Development" |
|||
|
|||
# Exit with success |
|||
exit 0 |
@ -1,174 +0,0 @@ |
|||
const { app, BrowserWindow } = require("electron"); |
|||
const path = require("path"); |
|||
const fs = require("fs"); |
|||
const logger = require("../utils/logger"); |
|||
|
|||
// 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"); |
|||
logger.log("Checking preload path:", preloadPath); |
|||
logger.log("Preload exists:", fs.existsSync(preloadPath)); |
|||
|
|||
// Create the browser window.
|
|||
const mainWindow = new BrowserWindow({ |
|||
width: 1200, |
|||
height: 800, |
|||
webPreferences: { |
|||
nodeIntegration: false, |
|||
contextIsolation: true, |
|||
webSecurity: true, |
|||
allowRunningInsecureContent: false, |
|||
preload: path.join(__dirname, "preload.js"), |
|||
}, |
|||
}); |
|||
|
|||
// Always open DevTools for now
|
|||
mainWindow.webContents.openDevTools(); |
|||
|
|||
// Intercept requests to fix asset paths
|
|||
mainWindow.webContents.session.webRequest.onBeforeRequest( |
|||
{ |
|||
urls: [ |
|||
"file://*/*/assets/*", |
|||
"file://*/assets/*", |
|||
"file:///assets/*", // Catch absolute paths
|
|||
"<all_urls>", // Catch all URLs as a fallback
|
|||
], |
|||
}, |
|||
(details, callback) => { |
|||
let url = details.url; |
|||
|
|||
// Handle paths that don't start with file://
|
|||
if (!url.startsWith("file://") && url.includes("/assets/")) { |
|||
url = `file://${path.join(__dirname, "www", url)}`; |
|||
} |
|||
|
|||
// Handle absolute paths starting with /assets/
|
|||
if (url.includes("/assets/") && !url.includes("/www/assets/")) { |
|||
const baseDir = url.includes("dist-electron") |
|||
? url.substring( |
|||
0, |
|||
url.indexOf("/dist-electron") + "/dist-electron".length, |
|||
) |
|||
: `file://${__dirname}`; |
|||
const assetPath = url.split("/assets/")[1]; |
|||
const newUrl = `${baseDir}/www/assets/${assetPath}`; |
|||
callback({ redirectURL: newUrl }); |
|||
return; |
|||
} |
|||
|
|||
callback({}); // No redirect for other URLs
|
|||
}, |
|||
); |
|||
|
|||
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
|
|||
mainWindow.webContents.session.webRequest.onHeadersReceived( |
|||
(details, callback) => { |
|||
callback({ |
|||
responseHeaders: { |
|||
...details.responseHeaders, |
|||
"Content-Security-Policy": [ |
|||
"default-src 'self';" + |
|||
"connect-src 'self' https://api.endorser.ch https://*.timesafari.app;" + |
|||
"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
|
|||
mainWindow.webContents.on( |
|||
"did-fail-load", |
|||
(event, errorCode, errorDescription) => { |
|||
logger.error("Page failed to load:", errorCode, errorDescription); |
|||
}, |
|||
); |
|||
|
|||
mainWindow.webContents.on("preload-error", (event, preloadPath, error) => { |
|||
logger.error("Preload script error:", preloadPath, error); |
|||
}); |
|||
|
|||
mainWindow.webContents.on( |
|||
"console-message", |
|||
(event, level, message, line, sourceId) => { |
|||
logger.log("Renderer Console:", line, sourceId, message); |
|||
}, |
|||
); |
|||
|
|||
// Enable remote debugging when in dev mode
|
|||
if (isDev) { |
|||
mainWindow.webContents.openDevTools(); |
|||
} |
|||
} |
|||
|
|||
// Handle app ready
|
|||
app.whenReady().then(createWindow); |
|||
|
|||
// Handle all windows closed
|
|||
app.on("window-all-closed", () => { |
|||
if (process.platform !== "darwin") { |
|||
app.quit(); |
|||
} |
|||
}); |
|||
|
|||
app.on("activate", () => { |
|||
if (BrowserWindow.getAllWindows().length === 0) { |
|||
createWindow(); |
|||
} |
|||
}); |
|||
|
|||
// Handle any errors
|
|||
process.on("uncaughtException", (error) => { |
|||
logger.error("Uncaught Exception:", error); |
|||
}); |
@ -1,221 +0,0 @@ |
|||
import { app, BrowserWindow } from "electron"; |
|||
import path from "path"; |
|||
import fs from "fs"; |
|||
|
|||
// Simple logger implementation
|
|||
const logger = { |
|||
// eslint-disable-next-line no-console
|
|||
log: (...args: unknown[]) => console.log(...args), |
|||
// eslint-disable-next-line no-console
|
|||
error: (...args: unknown[]) => console.error(...args), |
|||
// eslint-disable-next-line no-console
|
|||
info: (...args: unknown[]) => console.info(...args), |
|||
// eslint-disable-next-line no-console
|
|||
warn: (...args: unknown[]) => console.warn(...args), |
|||
// eslint-disable-next-line no-console
|
|||
debug: (...args: unknown[]) => console.debug(...args), |
|||
}; |
|||
|
|||
// Check if running in dev mode
|
|||
const isDev = process.argv.includes("--inspect"); |
|||
|
|||
async function createWindow(): Promise<void> { |
|||
// Add before createWindow function
|
|||
const preloadPath = app.isPackaged |
|||
? path.join(app.getAppPath(), "dist-electron", "preload.js") |
|||
: path.join(__dirname, "preload.js"); |
|||
logger.log("Checking preload path:", preloadPath); |
|||
logger.log("Preload exists:", fs.existsSync(preloadPath)); |
|||
|
|||
// Log environment and paths
|
|||
logger.log("process.cwd():", process.cwd()); |
|||
logger.log("__dirname:", __dirname); |
|||
logger.log("app.getAppPath():", app.getAppPath()); |
|||
logger.log("app.isPackaged:", app.isPackaged); |
|||
|
|||
// List files in __dirname and __dirname/www
|
|||
try { |
|||
logger.log("Files in __dirname:", fs.readdirSync(__dirname)); |
|||
const wwwDir = path.join(__dirname, "www"); |
|||
if (fs.existsSync(wwwDir)) { |
|||
logger.log("Files in www:", fs.readdirSync(wwwDir)); |
|||
} else { |
|||
logger.log("www directory does not exist in __dirname"); |
|||
} |
|||
} catch (e) { |
|||
logger.error("Error reading directories:", e); |
|||
} |
|||
|
|||
// Create the browser window.
|
|||
const mainWindow = new BrowserWindow({ |
|||
width: 1200, |
|||
height: 800, |
|||
webPreferences: { |
|||
nodeIntegration: false, |
|||
contextIsolation: true, |
|||
webSecurity: true, |
|||
allowRunningInsecureContent: false, |
|||
preload: preloadPath, |
|||
}, |
|||
}); |
|||
|
|||
// Always open DevTools for now
|
|||
mainWindow.webContents.openDevTools(); |
|||
|
|||
// Intercept requests to robustly fix asset paths for Electron
|
|||
mainWindow.webContents.session.webRequest.onBeforeRequest( |
|||
{ urls: ["*://*/*"] }, |
|||
(details, callback) => { |
|||
const url = details.url; |
|||
logger.log('[main.ts] Intercepted request:', url); |
|||
|
|||
// Match both file:///assets/... and /assets/...
|
|||
const assetMatch = url.match(/(?:file:\/\/\/|file:\/\/|https?:\/\/[^\/]+)?\/assets\/(.+)/); |
|||
if (assetMatch) { |
|||
const assetRelPath = assetMatch[1]; |
|||
const assetAbsPath = path.join(app.getAppPath(), "dist-electron", "www", "assets", assetRelPath); |
|||
logger.log('[main.ts] Asset request detected:', { |
|||
originalUrl: url, |
|||
assetRelPath, |
|||
assetAbsPath, |
|||
exists: fs.existsSync(assetAbsPath) |
|||
}); |
|||
|
|||
if (fs.existsSync(assetAbsPath)) { |
|||
const newUrl = `file://${assetAbsPath}`; |
|||
logger.log('[main.ts] Redirecting to:', newUrl); |
|||
callback({ redirectURL: newUrl }); |
|||
return; |
|||
} else { |
|||
logger.error('[main.ts] Asset file not found:', assetAbsPath); |
|||
} |
|||
} |
|||
callback({}); |
|||
} |
|||
); |
|||
|
|||
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()); |
|||
} |
|||
|
|||
let indexPath: string; |
|||
if (app.isPackaged) { |
|||
indexPath = path.join( |
|||
app.getAppPath(), |
|||
"dist-electron", |
|||
"www", |
|||
"index.html", |
|||
); |
|||
logger.log("[main.ts] Using packaged indexPath:", indexPath); |
|||
} else { |
|||
indexPath = path.resolve( |
|||
process.cwd(), |
|||
"dist-electron", |
|||
"www", |
|||
"index.html", |
|||
); |
|||
logger.log("[main.ts] Using dev indexPath:", indexPath); |
|||
if (!fs.existsSync(indexPath)) { |
|||
logger.error("[main.ts] Dev index.html not found:", indexPath); |
|||
throw new Error("Index file not found"); |
|||
} |
|||
} |
|||
|
|||
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")); |
|||
} |
|||
|
|||
// Add CSP headers to allow API connections
|
|||
mainWindow.webContents.session.webRequest.onHeadersReceived( |
|||
(details, callback) => { |
|||
callback({ |
|||
responseHeaders: { |
|||
...details.responseHeaders, |
|||
"Content-Security-Policy": [ |
|||
"default-src 'self';" + |
|||
"connect-src 'self' https://api.endorser.ch https://*.timesafari.app;" + |
|||
"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 with the correct base URL for assets
|
|||
const baseURL = `file://${path.dirname(indexPath)}/`; |
|||
logger.log('[main.ts] Loading with base URL:', baseURL); |
|||
|
|||
try { |
|||
await mainWindow.loadURL(`file://${indexPath}`); |
|||
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
|
|||
mainWindow.webContents.on( |
|||
"did-fail-load", |
|||
(_event, errorCode, errorDescription) => { |
|||
logger.error("Page failed to load:", errorCode, errorDescription); |
|||
}, |
|||
); |
|||
|
|||
mainWindow.webContents.on("preload-error", (_event, preloadPath, error) => { |
|||
logger.error("Preload script error:", preloadPath, error); |
|||
}); |
|||
|
|||
mainWindow.webContents.on( |
|||
"console-message", |
|||
(_event, _level, message, line, sourceId) => { |
|||
logger.log("Renderer Console:", line, sourceId, message); |
|||
}, |
|||
); |
|||
|
|||
// Enable remote debugging when in dev mode
|
|||
if (isDev) { |
|||
mainWindow.webContents.openDevTools(); |
|||
} |
|||
} |
|||
|
|||
// Handle app ready
|
|||
app.whenReady().then(createWindow); |
|||
|
|||
// Handle all windows closed
|
|||
app.on("window-all-closed", () => { |
|||
if (process.platform !== "darwin") { |
|||
app.quit(); |
|||
} |
|||
}); |
|||
|
|||
app.on("activate", () => { |
|||
if (BrowserWindow.getAllWindows().length === 0) { |
|||
createWindow(); |
|||
} |
|||
}); |
|||
|
|||
// Handle any errors
|
|||
process.on("uncaughtException", (error) => { |
|||
logger.error("Uncaught Exception:", error); |
|||
}); |
@ -1,91 +0,0 @@ |
|||
const { contextBridge, ipcRenderer } = require("electron"); |
|||
|
|||
const logger = { |
|||
log: (message, ...args) => { |
|||
// Always log in development, log with context in production
|
|||
if (process.env.NODE_ENV !== "production") { |
|||
/* eslint-disable no-console */ |
|||
console.log(`[Preload] ${message}`, ...args); |
|||
/* eslint-enable no-console */ |
|||
} |
|||
}, |
|||
warn: (message, ...args) => { |
|||
// Always log warnings
|
|||
/* eslint-disable no-console */ |
|||
console.warn(`[Preload] ${message}`, ...args); |
|||
/* eslint-enable no-console */ |
|||
}, |
|||
error: (message, ...args) => { |
|||
// Always log errors
|
|||
/* eslint-disable no-console */ |
|||
console.error(`[Preload] ${message}`, ...args); |
|||
/* eslint-enable no-console */ |
|||
}, |
|||
info: (message, ...args) => { |
|||
// Always log info in development, log with context in production
|
|||
if (process.env.NODE_ENV !== "production") { |
|||
/* eslint-disable no-console */ |
|||
console.info(`[Preload] ${message}`, ...args); |
|||
/* 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.info("Preload script starting..."); |
|||
|
|||
// Force electron platform in the renderer process
|
|||
window.process = { env: { VITE_PLATFORM: "electron" } }; |
|||
|
|||
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", |
|||
platform: "electron", // Explicitly set platform
|
|||
}, |
|||
// Path utilities
|
|||
getBasePath: () => { |
|||
return process.env.NODE_ENV === "development" ? "/" : "./"; |
|||
}, |
|||
}); |
|||
|
|||
logger.info("Preload script completed successfully"); |
|||
} catch (error) { |
|||
logger.error("Error in preload script:", error); |
|||
} |
@ -1,17 +0,0 @@ |
|||
import './assets/styles/tailwind.css'; |
|||
import { initializeApp } from "./main.common"; |
|||
import { logger } from "./utils/logger"; |
|||
|
|||
const platform = process.env.VITE_PLATFORM; |
|||
const pwa_enabled = process.env.VITE_PWA_ENABLED === "true"; |
|||
|
|||
logger.info("[Electron] Initializing app"); |
|||
logger.info("[Electron] Platform:", { platform }); |
|||
logger.info("[Electron] PWA enabled:", { pwa_enabled }); |
|||
|
|||
if (pwa_enabled) { |
|||
logger.warn("[Electron] PWA is enabled, but not supported in electron"); |
|||
} |
|||
|
|||
const app = initializeApp(); |
|||
app.mount("#app"); |
@ -1,358 +0,0 @@ |
|||
import { |
|||
ImageResult, |
|||
PlatformService, |
|||
PlatformCapabilities, |
|||
} from "../PlatformService"; |
|||
import { logger } from "../../utils/logger"; |
|||
import { QueryExecResult, SqlValue } from "@/interfaces/database"; |
|||
import { |
|||
SQLiteConnection, |
|||
SQLiteDBConnection, |
|||
CapacitorSQLite, |
|||
Changes, |
|||
} from "@capacitor-community/sqlite"; |
|||
import { DEFAULT_ENDORSER_API_SERVER } from "@/constants/app"; |
|||
|
|||
interface Migration { |
|||
name: string; |
|||
sql: string; |
|||
} |
|||
|
|||
/** |
|||
* Platform service implementation for Electron (desktop) platform. |
|||
* Provides native desktop functionality through Electron and Capacitor plugins for: |
|||
* - File system operations (TODO) |
|||
* - Camera integration (TODO) |
|||
* - SQLite database operations |
|||
* - System-level features (TODO) |
|||
*/ |
|||
export class ElectronPlatformService implements PlatformService { |
|||
private sqlite: SQLiteConnection; |
|||
private db: SQLiteDBConnection | null = null; |
|||
private dbName = "timesafari.db"; |
|||
private initialized = false; |
|||
|
|||
constructor() { |
|||
this.sqlite = new SQLiteConnection(CapacitorSQLite); |
|||
} |
|||
|
|||
private async initializeDatabase(): Promise<void> { |
|||
if (this.initialized) { |
|||
return; |
|||
} |
|||
|
|||
try { |
|||
// Create/Open database
|
|||
this.db = await this.sqlite.createConnection( |
|||
this.dbName, |
|||
false, |
|||
"no-encryption", |
|||
1, |
|||
false, |
|||
); |
|||
|
|||
await this.db.open(); |
|||
|
|||
// Set journal mode to WAL for better performance
|
|||
await this.db.execute("PRAGMA journal_mode=WAL;"); |
|||
|
|||
// Run migrations
|
|||
await this.runMigrations(); |
|||
|
|||
this.initialized = true; |
|||
logger.log( |
|||
"[ElectronPlatformService] SQLite database initialized successfully", |
|||
); |
|||
} catch (error) { |
|||
logger.error( |
|||
"[ElectronPlatformService] Error initializing SQLite database:", |
|||
error, |
|||
); |
|||
throw new Error( |
|||
"[ElectronPlatformService] Failed to initialize database", |
|||
); |
|||
} |
|||
} |
|||
|
|||
private async runMigrations(): Promise<void> { |
|||
if (!this.db) { |
|||
throw new Error("Database not initialized"); |
|||
} |
|||
|
|||
// Create migrations table if it doesn't exist
|
|||
await this.db.execute(` |
|||
CREATE TABLE IF NOT EXISTS migrations ( |
|||
id INTEGER PRIMARY KEY AUTOINCREMENT, |
|||
name TEXT NOT NULL UNIQUE, |
|||
executed_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP |
|||
); |
|||
`);
|
|||
|
|||
// Get list of executed migrations
|
|||
const result = await this.db.query("SELECT name FROM migrations;"); |
|||
const executedMigrations = new Set( |
|||
result.values?.map((row) => row[0]) || [], |
|||
); |
|||
|
|||
// Run pending migrations in order
|
|||
const migrations: Migration[] = [ |
|||
{ |
|||
name: "001_initial", |
|||
sql: ` |
|||
CREATE TABLE IF NOT EXISTS accounts ( |
|||
id INTEGER PRIMARY KEY AUTOINCREMENT, |
|||
dateCreated TEXT NOT NULL, |
|||
derivationPath TEXT, |
|||
did TEXT NOT NULL, |
|||
identityEncrBase64 TEXT, |
|||
mnemonicEncrBase64 TEXT, |
|||
passkeyCredIdHex TEXT, |
|||
publicKeyHex TEXT NOT NULL |
|||
); |
|||
|
|||
CREATE INDEX IF NOT EXISTS idx_accounts_did ON accounts(did); |
|||
|
|||
CREATE TABLE IF NOT EXISTS secret ( |
|||
id INTEGER PRIMARY KEY AUTOINCREMENT, |
|||
secretBase64 TEXT NOT NULL |
|||
); |
|||
|
|||
CREATE TABLE IF NOT EXISTS settings ( |
|||
id INTEGER PRIMARY KEY AUTOINCREMENT, |
|||
accountDid TEXT, |
|||
activeDid TEXT, |
|||
apiServer TEXT, |
|||
filterFeedByNearby BOOLEAN, |
|||
filterFeedByVisible BOOLEAN, |
|||
finishedOnboarding BOOLEAN, |
|||
firstName TEXT, |
|||
hideRegisterPromptOnNewContact BOOLEAN, |
|||
isRegistered BOOLEAN, |
|||
lastName TEXT, |
|||
lastAckedOfferToUserJwtId TEXT, |
|||
lastAckedOfferToUserProjectsJwtId TEXT, |
|||
lastNotifiedClaimId TEXT, |
|||
lastViewedClaimId TEXT, |
|||
notifyingNewActivityTime TEXT, |
|||
notifyingReminderMessage TEXT, |
|||
notifyingReminderTime TEXT, |
|||
partnerApiServer TEXT, |
|||
passkeyExpirationMinutes INTEGER, |
|||
profileImageUrl TEXT, |
|||
searchBoxes TEXT, |
|||
showContactGivesInline BOOLEAN, |
|||
showGeneralAdvanced BOOLEAN, |
|||
showShortcutBvc BOOLEAN, |
|||
vapid TEXT, |
|||
warnIfProdServer BOOLEAN, |
|||
warnIfTestServer BOOLEAN, |
|||
webPushServer TEXT |
|||
); |
|||
|
|||
CREATE INDEX IF NOT EXISTS idx_settings_accountDid ON settings(accountDid); |
|||
|
|||
INSERT INTO settings (id, apiServer) VALUES (1, '${DEFAULT_ENDORSER_API_SERVER}'); |
|||
|
|||
CREATE TABLE IF NOT EXISTS contacts ( |
|||
id INTEGER PRIMARY KEY AUTOINCREMENT, |
|||
did TEXT NOT NULL, |
|||
name TEXT, |
|||
contactMethods TEXT, |
|||
nextPubKeyHashB64 TEXT, |
|||
notes TEXT, |
|||
profileImageUrl TEXT, |
|||
publicKeyBase64 TEXT, |
|||
seesMe BOOLEAN, |
|||
registered BOOLEAN |
|||
); |
|||
|
|||
CREATE INDEX IF NOT EXISTS idx_contacts_did ON contacts(did); |
|||
CREATE INDEX IF NOT EXISTS idx_contacts_name ON contacts(name); |
|||
|
|||
CREATE TABLE IF NOT EXISTS logs ( |
|||
date TEXT PRIMARY KEY, |
|||
message TEXT NOT NULL |
|||
); |
|||
|
|||
CREATE TABLE IF NOT EXISTS temp ( |
|||
id TEXT PRIMARY KEY, |
|||
blobB64 TEXT |
|||
); |
|||
`,
|
|||
}, |
|||
]; |
|||
|
|||
for (const migration of migrations) { |
|||
if (!executedMigrations.has(migration.name)) { |
|||
await this.db.execute(migration.sql); |
|||
await this.db.run("INSERT INTO migrations (name) VALUES (?)", [ |
|||
migration.name, |
|||
]); |
|||
logger.log(`Migration ${migration.name} executed successfully`); |
|||
} |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* Gets the capabilities of the Electron platform |
|||
* @returns Platform capabilities object |
|||
*/ |
|||
getCapabilities(): PlatformCapabilities { |
|||
return { |
|||
hasFileSystem: false, // Not implemented yet
|
|||
hasCamera: false, // Not implemented yet
|
|||
isMobile: false, |
|||
isIOS: false, |
|||
hasFileDownload: false, // Not implemented yet
|
|||
needsFileHandlingInstructions: false, |
|||
isNativeApp: true, // Electron is a native app
|
|||
}; |
|||
} |
|||
|
|||
/** |
|||
* Reads a file from the filesystem. |
|||
* @param _path - Path to the file to read |
|||
* @returns Promise that should resolve to file contents |
|||
* @throws Error with "Not implemented" message |
|||
* @todo Implement file reading using Electron's file system API |
|||
*/ |
|||
async readFile(_path: string): Promise<string> { |
|||
throw new Error("Not implemented"); |
|||
} |
|||
|
|||
/** |
|||
* Writes content to a file. |
|||
* @param _path - Path where to write the file |
|||
* @param _content - Content to write to the file |
|||
* @throws Error with "Not implemented" message |
|||
* @todo Implement file writing using Electron's file system API |
|||
*/ |
|||
async writeFile(_path: string, _content: string): Promise<void> { |
|||
throw new Error("Not implemented"); |
|||
} |
|||
|
|||
/** |
|||
* Writes content to a file and opens the system share dialog. |
|||
* @param _fileName - Name of the file to create |
|||
* @param _content - Content to write to the file |
|||
* @throws Error with "Not implemented" message |
|||
* @todo Implement using Electron's dialog and file system APIs |
|||
*/ |
|||
async writeAndShareFile(_fileName: string, _content: string): Promise<void> { |
|||
throw new Error("Not implemented"); |
|||
} |
|||
|
|||
/** |
|||
* Deletes a file from the filesystem. |
|||
* @param _path - Path to the file to delete |
|||
* @throws Error with "Not implemented" message |
|||
* @todo Implement file deletion using Electron's file system API |
|||
*/ |
|||
async deleteFile(_path: string): Promise<void> { |
|||
throw new Error("Not implemented"); |
|||
} |
|||
|
|||
/** |
|||
* Lists files in the specified directory. |
|||
* @param _directory - Path to the directory to list |
|||
* @returns Promise that should resolve to array of filenames |
|||
* @throws Error with "Not implemented" message |
|||
* @todo Implement directory listing using Electron's file system API |
|||
*/ |
|||
async listFiles(_directory: string): Promise<string[]> { |
|||
throw new Error("Not implemented"); |
|||
} |
|||
|
|||
/** |
|||
* Should open system camera to take a picture. |
|||
* @returns Promise that should resolve to captured image data |
|||
* @throws Error with "Not implemented" message |
|||
* @todo Implement camera access using Electron's media APIs |
|||
*/ |
|||
async takePicture(): Promise<ImageResult> { |
|||
logger.error("takePicture not implemented in Electron platform"); |
|||
throw new Error("Not implemented"); |
|||
} |
|||
|
|||
/** |
|||
* Should open system file picker for selecting an image. |
|||
* @returns Promise that should resolve to selected image data |
|||
* @throws Error with "Not implemented" message |
|||
* @todo Implement file picker using Electron's dialog API |
|||
*/ |
|||
async pickImage(): Promise<ImageResult> { |
|||
logger.error("pickImage not implemented in Electron platform"); |
|||
throw new Error("Not implemented"); |
|||
} |
|||
|
|||
/** |
|||
* Should handle deep link URLs for the desktop application. |
|||
* @param _url - The deep link URL to handle |
|||
* @throws Error with "Not implemented" message |
|||
* @todo Implement deep link handling using Electron's protocol handler |
|||
*/ |
|||
async handleDeepLink(_url: string): Promise<void> { |
|||
logger.error("handleDeepLink not implemented in Electron platform"); |
|||
throw new Error("Not implemented"); |
|||
} |
|||
|
|||
/** |
|||
* @see PlatformService.dbQuery |
|||
*/ |
|||
async dbQuery(sql: string, params?: unknown[]): Promise<QueryExecResult> { |
|||
await this.initializeDatabase(); |
|||
if (!this.db) { |
|||
throw new Error("Database not initialized"); |
|||
} |
|||
|
|||
try { |
|||
const result = await this.db.query(sql, params || []); |
|||
const values = result.values || []; |
|||
return { |
|||
columns: [], // SQLite plugin doesn't provide column names in query result
|
|||
values: values as SqlValue[][], |
|||
}; |
|||
} catch (error) { |
|||
logger.error("Error executing query:", error); |
|||
throw new Error( |
|||
`Database query failed: ${error instanceof Error ? error.message : String(error)}`, |
|||
); |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* @see PlatformService.dbExec |
|||
*/ |
|||
async dbExec( |
|||
sql: string, |
|||
params?: unknown[], |
|||
): Promise<{ changes: number; lastId?: number }> { |
|||
await this.initializeDatabase(); |
|||
if (!this.db) { |
|||
throw new Error("Database not initialized"); |
|||
} |
|||
|
|||
try { |
|||
const result = await this.db.run(sql, params || []); |
|||
const changes = result.changes as Changes; |
|||
return { |
|||
changes: changes?.changes || 0, |
|||
lastId: changes?.lastId, |
|||
}; |
|||
} catch (error) { |
|||
logger.error("Error executing statement:", error); |
|||
throw new Error( |
|||
`Database execution failed: ${error instanceof Error ? error.message : String(error)}`, |
|||
); |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* Rotates the camera between front and back cameras. |
|||
* @returns Promise that resolves when the camera is rotated |
|||
* @throws Error indicating camera rotation is not implemented in Electron |
|||
*/ |
|||
async rotateCamera(): Promise<void> { |
|||
throw new Error("Camera rotation not implemented in Electron platform"); |
|||
} |
|||
} |
@ -1,28 +0,0 @@ |
|||
{ |
|||
"extends": "./tsconfig.json", |
|||
"compilerOptions": { |
|||
"module": "ESNext", |
|||
"moduleResolution": "bundler", |
|||
"target": "ES2020", |
|||
"outDir": "dist-electron", |
|||
"rootDir": "src", |
|||
"sourceMap": true, |
|||
"esModuleInterop": true, |
|||
"allowJs": true, |
|||
"resolveJsonModule": true, |
|||
"isolatedModules": true, |
|||
"noEmit": true, |
|||
"allowImportingTsExtensions": true, |
|||
"types": ["vite/client"], |
|||
"paths": { |
|||
"@/*": ["./src/*"] |
|||
}, |
|||
"typeRoots": ["./node_modules/@types", "./src/types"] |
|||
}, |
|||
"include": [ |
|||
"src/**/*.ts", |
|||
"src/**/*.d.ts", |
|||
"src/**/*.tsx", |
|||
"src/**/*.vue" |
|||
] |
|||
} |
@ -1,154 +0,0 @@ |
|||
import { defineConfig, mergeConfig } from "vite"; |
|||
import { createBuildConfig } from "./vite.config.common.mts"; |
|||
import path from 'path'; |
|||
|
|||
export default defineConfig(async () => { |
|||
const baseConfig = await createBuildConfig('electron'); |
|||
|
|||
return mergeConfig(baseConfig, { |
|||
build: { |
|||
outDir: 'dist-electron', |
|||
rollupOptions: { |
|||
input: { |
|||
// Main process entry points |
|||
main: path.resolve(__dirname, 'src/electron/main.ts'), |
|||
preload: path.resolve(__dirname, 'src/electron/preload.js'), |
|||
// Renderer process entry point (the web app) |
|||
app: path.resolve(__dirname, 'index.html'), |
|||
}, |
|||
external: ['electron'], |
|||
output: { |
|||
format: 'cjs', |
|||
entryFileNames: (chunkInfo) => { |
|||
// Use different formats for main process vs renderer |
|||
if (chunkInfo.name === 'main' || chunkInfo.name === 'preload') { |
|||
return '[name].js'; |
|||
} |
|||
return 'assets/[name].[hash].js'; |
|||
}, |
|||
assetFileNames: (assetInfo) => { |
|||
// Keep main process files in root, others in assets |
|||
if (assetInfo.name === 'main.js' || assetInfo.name === 'preload.js') { |
|||
return '[name].[ext]'; |
|||
} |
|||
return 'assets/[name].[hash].[ext]'; |
|||
}, |
|||
chunkFileNames: 'assets/[name].[hash].js', |
|||
}, |
|||
}, |
|||
target: 'node18', |
|||
minify: false, |
|||
sourcemap: true, |
|||
}, |
|||
resolve: { |
|||
alias: { |
|||
'@': path.resolve(__dirname, 'src'), |
|||
}, |
|||
}, |
|||
optimizeDeps: { |
|||
include: ['@/utils/logger'] |
|||
}, |
|||
plugins: [ |
|||
{ |
|||
name: 'typescript-transform', |
|||
transform(code: string, id: string) { |
|||
if (id.endsWith('main.ts')) { |
|||
// Replace the logger import with inline logger |
|||
return code.replace( |
|||
/import\s*{\s*logger\s*}\s*from\s*['"]@\/utils\/logger['"];?/, |
|||
`const logger = { |
|||
log: (...args) => console.log(...args), |
|||
error: (...args) => console.error(...args), |
|||
info: (...args) => console.info(...args), |
|||
warn: (...args) => console.warn(...args), |
|||
debug: (...args) => console.debug(...args), |
|||
};` |
|||
); |
|||
} |
|||
return code; |
|||
} |
|||
}, |
|||
{ |
|||
name: 'remove-sw-imports', |
|||
transform(code: string, id: string) { |
|||
// Remove service worker imports and registrations |
|||
if (id.includes('registerServiceWorker') || |
|||
id.includes('register-service-worker') || |
|||
id.includes('sw_scripts') || |
|||
id.includes('PushNotificationPermission') || |
|||
code.includes('navigator.serviceWorker')) { |
|||
return { |
|||
code: code |
|||
.replace(/import.*registerServiceWorker.*$/mg, '') |
|||
.replace(/import.*register-service-worker.*$/mg, '') |
|||
.replace(/navigator\.serviceWorker/g, 'undefined') |
|||
.replace(/if\s*\([^)]*serviceWorker[^)]*\)\s*{[^}]*}/g, '') |
|||
.replace(/import.*workbox.*$/mg, '') |
|||
.replace(/importScripts\([^)]*\)/g, '') |
|||
}; |
|||
} |
|||
return code; |
|||
} |
|||
}, |
|||
{ |
|||
name: 'remove-sw-files', |
|||
enforce: 'pre', |
|||
resolveId(id: string) { |
|||
// Prevent service worker files from being included |
|||
if (id.includes('sw.js') || |
|||
id.includes('workbox') || |
|||
id.includes('registerSW.js') || |
|||
id.includes('manifest.webmanifest')) { |
|||
return '\0empty'; |
|||
} |
|||
return null; |
|||
}, |
|||
load(id: string) { |
|||
if (id === '\0empty') { |
|||
return 'export default {}'; |
|||
} |
|||
return null; |
|||
} |
|||
}, |
|||
{ |
|||
name: 'electron-css-injection', |
|||
enforce: 'post', |
|||
generateBundle(options, bundle) { |
|||
// Find the main CSS file |
|||
const cssAsset = Object.values(bundle).find( |
|||
(asset: any) => asset.type === 'asset' && asset.fileName?.endsWith('.css') |
|||
) as any; |
|||
|
|||
if (cssAsset) { |
|||
// Find the HTML file and inject CSS link |
|||
const htmlAsset = Object.values(bundle).find( |
|||
(asset: any) => asset.type === 'asset' && asset.fileName?.endsWith('.html') |
|||
) as any; |
|||
|
|||
if (htmlAsset) { |
|||
const cssHref = `./${cssAsset.fileName}`; |
|||
const cssLink = ` <link rel="stylesheet" href="${cssHref}">\n`; |
|||
|
|||
// Check if CSS link already exists |
|||
if (!htmlAsset.source.includes(cssHref)) { |
|||
// Inject CSS link after the title tag |
|||
htmlAsset.source = htmlAsset.source.replace( |
|||
/(<title>.*?<\/title>)/, |
|||
`$1\n${cssLink}` |
|||
); |
|||
console.log(`[electron-css-injection] Injected CSS link: ${cssHref}`); |
|||
} else { |
|||
console.log(`[electron-css-injection] CSS link already present: ${cssHref}`); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
], |
|||
ssr: { |
|||
noExternal: ['@/utils/logger'] |
|||
}, |
|||
base: './', |
|||
publicDir: 'public', |
|||
}); |
|||
}); |
@ -1,31 +0,0 @@ |
|||
import { defineConfig } from 'vite'; |
|||
import vue from '@vitejs/plugin-vue'; |
|||
import path from 'path'; |
|||
|
|||
export default defineConfig({ |
|||
plugins: [vue()], |
|||
base: './', |
|||
resolve: { |
|||
alias: { |
|||
'@': path.resolve(__dirname, 'src'), |
|||
}, |
|||
}, |
|||
build: { |
|||
outDir: 'dist-electron/www', |
|||
emptyOutDir: false, |
|||
assetsDir: 'assets', |
|||
cssCodeSplit: false, |
|||
rollupOptions: { |
|||
input: path.resolve(__dirname, 'src/main.electron.ts'), |
|||
output: { |
|||
entryFileNames: 'main.electron.js', |
|||
assetFileNames: 'assets/[name]-[hash][extname]', |
|||
chunkFileNames: 'assets/[name]-[hash].js', |
|||
manualChunks: undefined |
|||
} |
|||
}, |
|||
commonjsOptions: { |
|||
transformMixedEsModules: true |
|||
} |
|||
} |
|||
}); |
Loading…
Reference in new issue