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