From 70c0edbed04ba4f9e51c2ea89ec6d48a27f60dab Mon Sep 17 00:00:00 2001 From: Matthew Raymer Date: Thu, 29 May 2025 10:18:07 +0000 Subject: [PATCH] fix: SQLite plugin initialization in Electron main process - Changed from direct plugin usage to SQLiteConnection pattern - Matches how platform services use the SQLite plugin - Removed handle() method dependency - Added proper method routing in IPC handler The app now launches without initialization errors. Next steps: - Test actual SQLite operations (createConnection, query, etc.) - Verify database creation and access - Add error handling for database operations --- capacitor.config.json | 11 +- package.json | 5 +- scripts/build-electron.js | 18 ++++ src/electron/main.ts | 104 +++++++++++++++++-- src/main.common.ts | 1 + src/main.electron.ts | 7 -- vite.config.electron.mts | 204 +++++++++++++++++--------------------- 7 files changed, 221 insertions(+), 129 deletions(-) diff --git a/capacitor.config.json b/capacitor.config.json index f43f3ea3..d7d003d0 100644 --- a/capacitor.config.json +++ b/capacitor.config.json @@ -1,10 +1,11 @@ { - "appId": "app.timesafari", + "appId": "com.timesafari.app", "appName": "TimeSafari", "webDir": "dist", "bundledWebRuntime": false, "server": { - "cleartext": true + "cleartext": true, + "androidScheme": "https" }, "plugins": { "App": { @@ -29,6 +30,12 @@ "biometricAuth": true, "biometricTitle": "Biometric login for TimeSafari" } + }, + "CapacitorSQLite": { + "electronIsEncryption": false, + "electronMacLocation": "~/Library/Application Support/TimeSafari", + "electronWindowsLocation": "C:\\ProgramData\\TimeSafari", + "electronLinuxLocation": "~/.local/share/TimeSafari" } }, "ios": { diff --git a/package.json b/package.json index 1cebe094..163658e9 100644 --- a/package.json +++ b/package.json @@ -23,7 +23,7 @@ "clean:electron": "rimraf dist-electron", "build:pywebview": "vite build --config vite.config.pywebview.mts", "build:web": "VITE_GIT_HASH=`git log -1 --pretty=format:%h` vite build --config vite.config.web.mts", - "build:web:electron": "VITE_GIT_HASH=`git log -1 --pretty=format:%h` vite build --config vite.config.electron.mts --mode electron", + "build:web:electron": "VITE_GIT_HASH=`git log -1 --pretty=format:%h` vite build --config vite.config.web.mts && VITE_GIT_HASH=`git log -1 --pretty=format:%h` vite build --config vite.config.electron.mts --mode electron", "build:electron": "npm run clean:electron && npm run build:web:electron && tsc -p tsconfig.electron.json && vite build --config vite.config.electron.mts && node scripts/build-electron.js", "build:capacitor": "vite build --mode capacitor --config vite.config.capacitor.mts", "electron:dev": "npm run build && electron .", @@ -181,7 +181,8 @@ }, "files": [ "dist-electron/**/*", - "dist/**/*" + "dist/**/*", + "capacitor.config.json" ], "extraResources": [ { diff --git a/scripts/build-electron.js b/scripts/build-electron.js index dc754846..0a601184 100644 --- a/scripts/build-electron.js +++ b/scripts/build-electron.js @@ -1,6 +1,7 @@ const fs = require("fs"); const fse = require("fs-extra"); const path = require("path"); +const { execSync } = require('child_process'); console.log("Starting Electron build finalization..."); @@ -64,4 +65,21 @@ if (fs.existsSync(preloadSrc)) { console.error("Preload script not found at:", preloadSrc); } +// Copy capacitor.config.json to dist-electron +try { + console.log("Copying capacitor.config.json to dist-electron..."); + const configPath = path.join(process.cwd(), 'capacitor.config.json'); + const targetPath = path.join(process.cwd(), 'dist-electron', 'capacitor.config.json'); + + if (!fs.existsSync(configPath)) { + throw new Error('capacitor.config.json not found in project root'); + } + + fs.copyFileSync(configPath, targetPath); + console.log("Successfully copied capacitor.config.json"); +} catch (error) { + console.error("Failed to copy capacitor.config.json:", error); + throw error; +} + console.log("Electron index.html copied and patched for Electron context."); diff --git a/src/electron/main.ts b/src/electron/main.ts index 15fe43d1..2d6ae68c 100644 --- a/src/electron/main.ts +++ b/src/electron/main.ts @@ -1,7 +1,8 @@ import { app, BrowserWindow, ipcMain } from "electron"; import path from "path"; import fs from "fs"; -import { CapacitorSQLiteElectron } from "@capacitor-community/sqlite/electron/dist/plugin"; +import { Capacitor } from "@capacitor/core"; +import { SQLiteConnection } from "@capacitor-community/sqlite"; // Simple logger implementation const logger = { @@ -17,12 +18,94 @@ const logger = { debug: (...args: unknown[]) => console.debug("[Main]", ...args), }; +logger.info("Starting main process initialization..."); + +// Initialize Capacitor for Electron in main process +try { + logger.info("About to initialize Capacitor..."); + logger.info("Capacitor before init:", { + hasPlatform: 'platform' in Capacitor, + hasIsNativePlatform: 'isNativePlatform' in Capacitor, + platformType: typeof Capacitor.platform, + isNativePlatformType: typeof Capacitor.isNativePlatform + }); + + // Try direct assignment first + try { + (Capacitor as any).platform = 'electron'; + (Capacitor as any).isNativePlatform = true; + logger.info("Direct assignment successful"); + } catch (e) { + logger.warn("Direct assignment failed, trying defineProperty:", e); + Object.defineProperty(Capacitor, "isNativePlatform", { + get: () => true, + configurable: true, + }); + + Object.defineProperty(Capacitor, "platform", { + get: () => 'electron', + configurable: true, + }); + } + + logger.info("Capacitor after init:", { + platform: Capacitor.platform, + isNativePlatform: Capacitor.isNativePlatform, + platformType: typeof Capacitor.platform, + isNativePlatformType: typeof Capacitor.isNativePlatform + }); +} catch (error) { + logger.error("Failed to initialize Capacitor:", error); + throw error; +} + // Initialize SQLite plugin for Electron -let sqlitePlugin: CapacitorSQLiteElectron | null = null; +let sqliteConnection: SQLiteConnection | null = null; try { - logger.info("Initializing SQLite plugin..."); - sqlitePlugin = new CapacitorSQLiteElectron(); + logger.info("About to initialize SQLite plugin..."); + // Use require to ensure we get the CommonJS module + const sqliteModule = require('@capacitor-community/sqlite/electron/dist/plugin'); + logger.info("SQLite module loaded:", Object.keys(sqliteModule)); + + // Debug the module contents + logger.info("SQLite module contents:", { + default: typeof sqliteModule.default, + defaultKeys: sqliteModule.default ? Object.keys(sqliteModule.default) : [], + CapacitorSQLite: typeof sqliteModule.CapacitorSQLite, + CapacitorSQLiteKeys: sqliteModule.CapacitorSQLite ? Object.keys(sqliteModule.CapacitorSQLite) : [], + moduleType: typeof sqliteModule, + moduleKeys: Object.keys(sqliteModule), + hasHandle: sqliteModule.default?.handle ? 'yes' : 'no', + handleType: typeof sqliteModule.default?.handle, + defaultProto: Object.getPrototypeOf(sqliteModule.default), + defaultProtoKeys: Object.getPrototypeOf(sqliteModule.default) ? Object.keys(Object.getPrototypeOf(sqliteModule.default)) : [] + }); + + // Get the plugin constructor + const SQLitePlugin = sqliteModule.CapacitorSQLite; + logger.info("Got SQLite plugin constructor:", { + type: typeof SQLitePlugin, + name: SQLitePlugin?.name, + prototype: SQLitePlugin ? Object.getPrototypeOf(SQLitePlugin) : null, + prototypeKeys: SQLitePlugin ? Object.keys(Object.getPrototypeOf(SQLitePlugin)) : [] + }); + + if (typeof SQLitePlugin !== 'function') { + throw new Error(`SQLite plugin is not a constructor, got ${typeof SQLitePlugin}`); + } + + // Create a connection using the plugin + logger.info("Creating SQLite connection..."); + sqliteConnection = new SQLiteConnection(SQLitePlugin); + + logger.info("SQLite connection created:", { + type: typeof sqliteConnection, + keys: Object.keys(sqliteConnection), + prototype: Object.getPrototypeOf(sqliteConnection), + prototypeKeys: Object.getPrototypeOf(sqliteConnection) ? Object.keys(Object.getPrototypeOf(sqliteConnection)) : [] + }); + logger.info("SQLite plugin initialized successfully"); } catch (error) { logger.error("Failed to initialize SQLite plugin:", error); @@ -31,15 +114,22 @@ try { // Set up IPC handler for SQLite ipcMain.handle("capacitor-sqlite", async (_event, ...args) => { - if (!sqlitePlugin) { - const error = new Error("SQLite plugin not initialized"); + if (!sqliteConnection) { + const error = new Error("SQLite connection not initialized"); logger.error(error); throw error; } try { logger.debug("Handling SQLite request:", args); - const result = await sqlitePlugin.handle(_event, ...args); + // The first argument should be the method name + const [method, ...restArgs] = args; + if (typeof method !== 'string' || !(method in sqliteConnection)) { + throw new Error(`Invalid SQLite method: ${method}`); + } + + // Call the method on the connection + const result = await (sqliteConnection as any)[method](...restArgs); logger.debug("SQLite request completed successfully"); return result; } catch (error) { diff --git a/src/main.common.ts b/src/main.common.ts index ce7b6495..f86f0458 100644 --- a/src/main.common.ts +++ b/src/main.common.ts @@ -2,6 +2,7 @@ import { createPinia } from "pinia"; import { App as VueApp, ComponentPublicInstance, createApp } from "vue"; import App from "./App.vue"; import router from "./router"; +// Use the browser version of axios for web builds import axios from "axios"; import VueAxios from "vue-axios"; import Notifications from "notiwind"; diff --git a/src/main.electron.ts b/src/main.electron.ts index be82a7b7..5be7b0ab 100644 --- a/src/main.electron.ts +++ b/src/main.electron.ts @@ -1,14 +1,7 @@ import { initializeApp } from "./main.common"; import { logger } from "./utils/logger"; -import { Capacitor } from "@capacitor/core"; import { CapacitorSQLite } from "@capacitor-community/sqlite"; -// Initialize Capacitor for Electron -Object.defineProperty(Capacitor, "isNativePlatform", { - get: () => true, - configurable: true, -}); - // Initialize SQLite plugin for Electron if (typeof window !== "undefined") { // Register the plugin globally diff --git a/vite.config.electron.mts b/vite.config.electron.mts index b9b8eea7..6291af92 100644 --- a/vite.config.electron.mts +++ b/vite.config.electron.mts @@ -1,120 +1,102 @@ -import { defineConfig, mergeConfig } from "vite"; -import { createBuildConfig } from "./vite.config.common.mts"; +import { defineConfig } from "vite"; import path from 'path'; -export default defineConfig(async ({ mode }) => { - const baseConfig = await createBuildConfig('electron'); - const isWebBuild = mode === 'electron'; - - return mergeConfig(baseConfig, { - build: { - outDir: isWebBuild ? 'dist' : 'dist-electron', - rollupOptions: isWebBuild ? { - // Web app build configuration - input: path.resolve(__dirname, 'index.html'), - output: { - format: 'esm', - entryFileNames: 'assets/[name]-[hash].js', - chunkFileNames: 'assets/[name]-[hash].js', - assetFileNames: 'assets/[name]-[hash].[ext]' - } - } : { - // Main process build configuration - input: { - main: path.resolve(__dirname, 'src/electron/main.ts'), - preload: path.resolve(__dirname, 'src/electron/preload.js'), - }, - external: [ - 'electron', - '@capacitor-community/sqlite', - '@capacitor-community/sqlite/electron', - 'better-sqlite3-multiple-ciphers' - ], - output: { - format: 'cjs', - entryFileNames: '[name].js', - assetFileNames: 'assets/[name].[ext]', - }, +export default defineConfig({ + build: { + outDir: 'dist-electron', + rollupOptions: { + input: { + main: path.resolve(__dirname, 'src/electron/main.ts'), + preload: path.resolve(__dirname, 'src/electron/preload.js'), }, - target: isWebBuild ? 'esnext' : 'node18', - minify: !isWebBuild, - sourcemap: true, - }, - resolve: { - alias: { - '@': path.resolve(__dirname, 'src'), + external: [ + // Node.js built-ins + 'stream', + 'path', + 'fs', + 'crypto', + 'buffer', + 'util', + 'events', + 'url', + 'assert', + 'os', + 'net', + 'http', + 'https', + 'zlib', + 'child_process', + // Electron and Capacitor + 'electron', + '@capacitor/core', + '@capacitor-community/sqlite', + '@capacitor-community/sqlite/electron', + '@capacitor-community/sqlite/electron/dist/plugin', + 'better-sqlite3-multiple-ciphers', + // HTTP clients + 'axios', + 'axios/dist/axios', + 'axios/dist/node/axios.cjs' + ], + output: { + format: 'cjs', + entryFileNames: '[name].js', + assetFileNames: 'assets/[name].[ext]', }, }, - optimizeDeps: { - include: ['@/utils/logger'] + target: 'node18', + minify: false, + sourcemap: true, + }, + resolve: { + alias: { + '@': path.resolve(__dirname, 'src'), + // Use Node.js version of axios in electron + 'axios': 'axios/dist/node/axios.cjs' }, - 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; + }, + optimizeDeps: { + exclude: [ + 'stream', + 'path', + 'fs', + 'crypto', + 'buffer', + 'util', + 'events', + 'url', + 'assert', + 'os', + 'net', + 'http', + 'https', + 'zlib', + 'child_process', + 'axios', + 'axios/dist/axios', + 'axios/dist/node/axios.cjs' + ] + }, + plugins: [ + { + name: 'typescript-transform', + transform(code: string, id: string) { + if (id.endsWith('main.ts')) { + 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; } - ], - ssr: { - noExternal: ['@/utils/logger'] - }, - base: './', - publicDir: 'public', - }); + } + ], + base: './', + publicDir: 'public', }); \ No newline at end of file