Browse Source

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
pull/134/head
Matthew Raymer 1 week ago
parent
commit
70c0edbed0
  1. 11
      capacitor.config.json
  2. 5
      package.json
  3. 18
      scripts/build-electron.js
  4. 104
      src/electron/main.ts
  5. 1
      src/main.common.ts
  6. 7
      src/main.electron.ts
  7. 204
      vite.config.electron.mts

11
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": {

5
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": [
{

18
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.");

104
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) {

1
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";

7
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

204
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',
});
Loading…
Cancel
Save