Browse Source

fix: rename postcss.config.js to .cjs for ES module compatibility

- Added "type": "module" to package.json to support ES module imports for Electron SQLite
- Renamed postcss.config.js to postcss.config.cjs to maintain CommonJS syntax
- This ensures build tools can properly load the PostCSS configuration
pull/134/head
Matthew Raymer 1 week ago
parent
commit
3997a88b44
  1. 69
      experiment.sh
  2. 9
      package.json
  3. 0
      postcss.config.cjs
  4. 4
      scripts/build-electron.js
  5. 136
      src/electron/main.ts
  6. 49
      src/electron/preload.js
  7. 15
      src/libs/endorserServer.ts
  8. 24
      src/main.electron.ts
  9. 8
      src/services/platforms/ElectronPlatformService.ts
  10. 44
      src/types/capacitor-sqlite-electron.d.ts
  11. 7
      src/types/global.d.ts
  12. 4
      vite.config.electron.mts

69
experiment.sh

@ -0,0 +1,69 @@
#! /bin/bash
# Exit on any error
set -e
# Function to check if a command exists
check_command() {
if ! command -v $1 &> /dev/null; then
echo "Error: $1 is required but not installed."
exit 1
fi
}
# Check required commands
check_command node
check_command npm
# Clean up previous builds
echo "Cleaning previous builds..."
rm -rf dist*
# Set environment variables for the build
echo "Setting up environment variables..."
export VITE_PLATFORM=electron
export VITE_PWA_ENABLED=false
export VITE_DISABLE_PWA=true
# Ensure TypeScript is installed
echo "Checking TypeScript installation..."
if [ ! -f "./node_modules/.bin/tsc" ]; then
echo "Installing TypeScript..."
npm install --save-dev typescript@~5.2.2
# Verify installation
if [ ! -f "./node_modules/.bin/tsc" ]; then
echo "TypeScript installation failed!"
exit 1
fi
fi
# Get git hash for versioning
GIT_HASH=$(git log -1 --pretty=format:%h)
# Build web assets
echo "Building web assets..."
VITE_GIT_HASH=$GIT_HASH npx vite build --config vite.config.app.electron.mts --mode electron
# TypeScript compilation
echo "Running TypeScript compilation..."
if ! ./node_modules/.bin/tsc -p tsconfig.electron.json; then
echo "TypeScript compilation failed!"
exit 1
fi
# Build electron main process
echo "Building electron main process..."
VITE_GIT_HASH=$GIT_HASH npx vite build --config vite.config.electron.mts --mode electron
# Organize files
echo "Organizing build files..."
mkdir -p dist-electron/www
cp -r dist/* dist-electron/www/
mkdir -p dist-electron/resources
cp src/electron/preload.js dist-electron/resources/preload.js
# Build the AppImage
echo "Building AppImage..."
npx electron-builder --linux AppImage
echo "Build completed successfully!"

9
package.json

@ -172,7 +172,7 @@
"vite": "^5.2.0", "vite": "^5.2.0",
"vite-plugin-pwa": "^1.0.0" "vite-plugin-pwa": "^1.0.0"
}, },
"main": "./dist-electron/main.js", "main": "./dist-electron/main.mjs",
"build": { "build": {
"appId": "app.timesafari", "appId": "app.timesafari",
"productName": "TimeSafari", "productName": "TimeSafari",
@ -190,8 +190,8 @@
"to": "www" "to": "www"
}, },
{ {
"from": "dist-electron/resources/preload.js", "from": "dist-electron/resources/preload.mjs",
"to": "preload.js" "to": "preload.mjs"
} }
], ],
"linux": { "linux": {
@ -229,5 +229,6 @@
} }
] ]
} }
} },
"type": "module"
} }

0
postcss.config.js → postcss.config.cjs

4
scripts/build-electron.js

@ -49,8 +49,8 @@ indexContent = indexContent.replace(
fs.writeFileSync(finalIndexPath, indexContent); fs.writeFileSync(finalIndexPath, indexContent);
// Copy preload script to resources // Copy preload script to resources
const preloadSrc = path.join(electronDistPath, "preload.js"); const preloadSrc = path.join(electronDistPath, "preload.mjs");
const preloadDest = path.join(electronDistPath, "resources", "preload.js"); const preloadDest = path.join(electronDistPath, "resources", "preload.mjs");
// Ensure resources directory exists // Ensure resources directory exists
const resourcesDir = path.join(electronDistPath, "resources"); const resourcesDir = path.join(electronDistPath, "resources");

136
src/electron/main.ts

@ -1,8 +1,12 @@
import { app, BrowserWindow, ipcMain } from "electron"; import { app, BrowserWindow, ipcMain } from "electron";
import path from "path";
import fs from "fs";
import { Capacitor } from "@capacitor/core"; import { Capacitor } from "@capacitor/core";
import { SQLiteConnection } from "@capacitor-community/sqlite"; import fs from "fs";
import path from "path";
import { fileURLToPath } from "url";
// Get __dirname equivalent in ES modules
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
// Simple logger implementation // Simple logger implementation
const logger = { const logger = {
@ -24,16 +28,17 @@ logger.info("Starting main process initialization...");
try { try {
logger.info("About to initialize Capacitor..."); logger.info("About to initialize Capacitor...");
logger.info("Capacitor before init:", { logger.info("Capacitor before init:", {
hasPlatform: 'platform' in Capacitor, hasPlatform: "platform" in Capacitor,
hasIsNativePlatform: 'isNativePlatform' in Capacitor, hasIsNativePlatform: "isNativePlatform" in Capacitor,
platformType: typeof Capacitor.platform, platformType: typeof Capacitor.platform,
isNativePlatformType: typeof Capacitor.isNativePlatform isNativePlatformType: typeof Capacitor.isNativePlatform,
}); });
// Try direct assignment first // Try direct assignment first
try { try {
(Capacitor as any).platform = 'electron'; (Capacitor as unknown as { platform: string }).platform = "electron";
(Capacitor as any).isNativePlatform = true; (Capacitor as unknown as { isNativePlatform: boolean }).isNativePlatform =
true;
logger.info("Direct assignment successful"); logger.info("Direct assignment successful");
} catch (e) { } catch (e) {
logger.warn("Direct assignment failed, trying defineProperty:", e); logger.warn("Direct assignment failed, trying defineProperty:", e);
@ -43,7 +48,7 @@ try {
}); });
Object.defineProperty(Capacitor, "platform", { Object.defineProperty(Capacitor, "platform", {
get: () => 'electron', get: () => "electron",
configurable: true, configurable: true,
}); });
} }
@ -52,90 +57,50 @@ try {
platform: Capacitor.platform, platform: Capacitor.platform,
isNativePlatform: Capacitor.isNativePlatform, isNativePlatform: Capacitor.isNativePlatform,
platformType: typeof Capacitor.platform, platformType: typeof Capacitor.platform,
isNativePlatformType: typeof Capacitor.isNativePlatform isNativePlatformType: typeof Capacitor.isNativePlatform,
}); });
} catch (error) { } catch (error) {
logger.error("Failed to initialize Capacitor:", error); logger.error("Failed to initialize Capacitor:", error);
throw error; throw error;
} }
// Initialize SQLite plugin for Electron // Initialize SQLite plugin
let sqliteConnection: SQLiteConnection | null = null; let sqlitePlugin: any = null;
try { async function initializeSQLite() {
logger.info("About to initialize SQLite plugin..."); try {
// Use require to ensure we get the CommonJS module // Import the plugin directly in the main process
const sqliteModule = require('@capacitor-community/sqlite/electron/dist/plugin'); const plugin = await import("@capacitor-community/sqlite/electron/dist/plugin.js");
logger.info("SQLite module loaded:", Object.keys(sqliteModule)); sqlitePlugin = new plugin.CapacitorSQLite();
// Debug the module contents // Test the plugin with a simple operation
logger.info("SQLite module contents:", { const result = await sqlitePlugin.echo({ value: "test" });
default: typeof sqliteModule.default, if (result.value !== "test") {
defaultKeys: sqliteModule.default ? Object.keys(sqliteModule.default) : [], throw new Error("SQLite plugin echo test failed");
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"); logger.info("SQLite plugin initialized successfully");
} catch (error) { } catch (error) {
logger.error("Failed to initialize SQLite plugin:", error); logger.error("Failed to initialize SQLite plugin:", error);
throw error; throw error;
}
} }
// Set up IPC handler for SQLite // Initialize SQLite plugin
ipcMain.handle("capacitor-sqlite", async (_event, ...args) => { initializeSQLite().catch(error => {
if (!sqliteConnection) { logger.error("Failed to initialize SQLite plugin:", error);
const error = new Error("SQLite connection not initialized"); });
logger.error(error);
throw error;
}
try { // Set up IPC handlers for SQLite operations
logger.debug("Handling SQLite request:", args); ipcMain.handle("check-sqlite-availability", () => {
// The first argument should be the method name return sqlitePlugin !== null;
const [method, ...restArgs] = args; });
if (typeof method !== 'string' || !(method in sqliteConnection)) {
throw new Error(`Invalid SQLite method: ${method}`);
}
// Call the method on the connection ipcMain.handle("capacitor-sqlite", async (event, ...args) => {
const result = await (sqliteConnection as any)[method](...restArgs); if (!sqlitePlugin) {
logger.debug("SQLite request completed successfully"); throw new Error("SQLite plugin not initialized");
return result;
} catch (error) {
logger.error("SQLite request failed:", error);
throw error;
} }
return sqlitePlugin.handle(event, ...args);
}); });
// Initialize app when ready // Initialize app when ready
@ -150,8 +115,8 @@ const isDev = process.argv.includes("--inspect");
function createWindow(): void { function createWindow(): void {
// Resolve preload path based on environment // Resolve preload path based on environment
const preloadPath = app.isPackaged const preloadPath = app.isPackaged
? path.join(process.resourcesPath, "preload.js") ? path.join(process.resourcesPath, "preload.mjs")
: path.join(__dirname, "preload.js"); : path.join(__dirname, "preload.mjs");
logger.log("[Electron] Preload path:", preloadPath); logger.log("[Electron] Preload path:", preloadPath);
logger.log("[Electron] Preload exists:", fs.existsSync(preloadPath)); logger.log("[Electron] Preload exists:", fs.existsSync(preloadPath));
@ -183,18 +148,17 @@ function createWindow(): void {
webPreferences: { webPreferences: {
nodeIntegration: false, nodeIntegration: false,
contextIsolation: true, contextIsolation: true,
webSecurity: true, sandbox: false,
allowRunningInsecureContent: false, preload: preloadPath,
preload: preloadPath, // Use the resolved preload path
}, },
}); });
// Track DevTools state // Track DevTools state
mainWindow.webContents.on('devtools-opened', () => { mainWindow.webContents.on("devtools-opened", () => {
logger.info("[Electron] DevTools opened"); logger.info("[Electron] DevTools opened");
}); });
mainWindow.webContents.on('devtools-closed', () => { mainWindow.webContents.on("devtools-closed", () => {
logger.warn("[Electron] DevTools closed - reopening"); logger.warn("[Electron] DevTools closed - reopening");
mainWindow.webContents.openDevTools(); mainWindow.webContents.openDevTools();
}); });
@ -215,7 +179,7 @@ function createWindow(): void {
urls: [ urls: [
"file://*/*/assets/*", "file://*/*/assets/*",
"file://*/assets/*", "file://*/assets/*",
"file:///assets/*" "file:///assets/*",
// Removed <all_urls> to reduce noise // Removed <all_urls> to reduce noise
], ],
}, },
@ -339,8 +303,8 @@ function createWindow(): void {
}, },
); );
mainWindow.webContents.openDevTools({ mode: 'detach' }); mainWindow.webContents.openDevTools({ mode: "detach" });
mainWindow.webContents.once('devtools-opened', () => { mainWindow.webContents.once("devtools-opened", () => {
if (mainWindow.webContents.devToolsWebContents) { if (mainWindow.webContents.devToolsWebContents) {
mainWindow.webContents.devToolsWebContents.focus(); mainWindow.webContents.devToolsWebContents.focus();
} }

49
src/electron/preload.js

@ -85,6 +85,55 @@ try {
}, },
}); });
// Expose protected methods that allow the renderer process to use
// the ipcRenderer without exposing the entire object
contextBridge.exposeInMainWorld(
'electron',
{
// SQLite plugin bridge
sqlite: {
// Check if SQLite is available
isAvailable: () => ipcRenderer.invoke('check-sqlite-availability'),
// Execute SQLite operations
execute: (method, ...args) => ipcRenderer.invoke('capacitor-sqlite', method, ...args),
},
// ... existing exposed methods ...
}
);
// Create a proxy for the CapacitorSQLite plugin
const createSQLiteProxy = () => {
return {
async createConnection(...args) {
return ipcRenderer.invoke('capacitor-sqlite', 'createConnection', ...args);
},
async isConnection(...args) {
return ipcRenderer.invoke('capacitor-sqlite', 'isConnection', ...args);
},
async retrieveConnection(...args) {
return ipcRenderer.invoke('capacitor-sqlite', 'retrieveConnection', ...args);
},
async retrieveAllConnections() {
return ipcRenderer.invoke('capacitor-sqlite', 'retrieveAllConnections');
},
async closeConnection(...args) {
return ipcRenderer.invoke('capacitor-sqlite', 'closeConnection', ...args);
},
async closeAllConnections() {
return ipcRenderer.invoke('capacitor-sqlite', 'closeAllConnections');
},
async isAvailable() {
return ipcRenderer.invoke('capacitor-sqlite', 'isAvailable');
},
async getPlatform() {
return 'electron';
},
};
};
// Expose the SQLite plugin proxy
contextBridge.exposeInMainWorld('CapacitorSQLite', createSQLiteProxy());
logger.info("Preload script completed successfully"); logger.info("Preload script completed successfully");
} catch (error) { } catch (error) {
logger.error("Error in preload script:", error); logger.error("Error in preload script:", error);

15
src/libs/endorserServer.ts

@ -685,6 +685,7 @@ export function hydrateGive(
if (amount && !isNaN(amount)) { if (amount && !isNaN(amount)) {
const quantitativeValue: QuantitativeValue = { const quantitativeValue: QuantitativeValue = {
"@type": "QuantitativeValue",
amountOfThisGood: amount, amountOfThisGood: amount,
unitCode: unitCode || "HUR", unitCode: unitCode || "HUR",
}; };
@ -870,7 +871,6 @@ export function hydrateOffer(
if (amount && !isNaN(amount)) { if (amount && !isNaN(amount)) {
const quantitativeValue: QuantitativeValue = { const quantitativeValue: QuantitativeValue = {
"@context": SCHEMA_ORG_CONTEXT,
"@type": "QuantitativeValue", "@type": "QuantitativeValue",
amountOfThisGood: amount, amountOfThisGood: amount,
unitCode: unitCode || "HUR", unitCode: unitCode || "HUR",
@ -997,11 +997,12 @@ export async function createAndSubmitClaim(
axios: Axios, axios: Axios,
): Promise<CreateAndSubmitClaimResult> { ): Promise<CreateAndSubmitClaimResult> {
try { try {
const vcPayload = { const vcPayload: { vc: VerifiableCredentialClaim } = {
vc: { vc: {
"@context": ["https://www.w3.org/2018/credentials/v1"], "@context": "https://www.w3.org/2018/credentials/v1",
"@type": "VerifiableCredential",
type: ["VerifiableCredential"], type: ["VerifiableCredential"],
credentialSubject: vcClaim, credentialSubject: vcClaim as unknown as ClaimObject,
}, },
}; };
@ -1307,7 +1308,7 @@ export async function createEndorserJwtVcFromClaim(
// Make a payload for the claim // Make a payload for the claim
const vcPayload = { const vcPayload = {
vc: { vc: {
"@context": ["https://www.w3.org/2018/credentials/v1"], "@context": "https://www.w3.org/2018/credentials/v1",
type: ["VerifiableCredential"], type: ["VerifiableCredential"],
credentialSubject: claim, credentialSubject: claim,
}, },
@ -1332,10 +1333,10 @@ export async function createInviteJwt(
// Make a payload for the claim // Make a payload for the claim
const vcPayload: { vc: VerifiableCredentialClaim } = { const vcPayload: { vc: VerifiableCredentialClaim } = {
vc: { vc: {
"@context": ["https://www.w3.org/2018/credentials/v1"], "@context": "https://www.w3.org/2018/credentials/v1",
"@type": "VerifiableCredential", "@type": "VerifiableCredential",
type: ["VerifiableCredential"], type: ["VerifiableCredential"],
credentialSubject: vcClaim as unknown as ClaimObject, // Type assertion needed due to object being string credentialSubject: vcClaim as unknown as ClaimObject,
}, },
}; };

24
src/main.electron.ts

@ -1,12 +1,17 @@
import { initializeApp } from "./main.common"; import { initializeApp } from "./main.common";
import { logger } from "./utils/logger"; import { logger } from "./utils/logger";
import { CapacitorSQLite } from "@capacitor-community/sqlite";
// Initialize SQLite plugin for Electron async function initializeSQLite() {
if (typeof window !== "undefined") { try {
// Register the plugin globally const isAvailable = await window.electron.sqlite.isAvailable();
window.CapacitorSQLite = CapacitorSQLite; if (!isAvailable) {
logger.info("[Electron] SQLite plugin initialized in native mode"); throw new Error("SQLite plugin not available in main process");
}
logger.info("[Electron] SQLite plugin bridge initialized");
} catch (error) {
logger.error("[Electron] Failed to initialize SQLite plugin bridge:", error);
throw error;
}
} }
const platform = process.env.VITE_PLATFORM; const platform = process.env.VITE_PLATFORM;
@ -20,5 +25,10 @@ if (pwa_enabled) {
logger.warn("[Electron] PWA is enabled, but not supported in electron"); logger.warn("[Electron] PWA is enabled, but not supported in electron");
} }
// Initialize app and SQLite
const app = initializeApp(); const app = initializeApp();
app.mount("#app"); initializeSQLite().then(() => {
app.mount("#app");
}).catch(error => {
logger.error("[Electron] Failed to initialize app:", error);
});

8
src/services/platforms/ElectronPlatformService.ts

@ -32,7 +32,7 @@ export class ElectronPlatformService implements PlatformService {
private initialized = false; private initialized = false;
constructor() { constructor() {
// Ensure we're using the native implementation // Use the IPC bridge for SQLite operations
if (!window.CapacitorSQLite) { if (!window.CapacitorSQLite) {
throw new Error("CapacitorSQLite not initialized in Electron"); throw new Error("CapacitorSQLite not initialized in Electron");
} }
@ -45,6 +45,12 @@ export class ElectronPlatformService implements PlatformService {
} }
try { try {
// Check if SQLite is available through IPC
const isAvailable = await window.electron.sqlite.isAvailable();
if (!isAvailable) {
throw new Error("SQLite is not available in the main process");
}
// Create/Open database with native implementation // Create/Open database with native implementation
this.db = await this.sqlite.createConnection( this.db = await this.sqlite.createConnection(
this.dbName, this.dbName,

44
src/types/capacitor-sqlite-electron.d.ts

@ -1,6 +1,46 @@
declare module '@capacitor-community/sqlite/electron/dist/plugin' { declare module '@capacitor-community/sqlite/electron/dist/plugin.js' {
export class CapacitorSQLiteElectron { export class CapacitorSQLite {
constructor(); constructor();
handle(event: Electron.IpcMainInvokeEvent, ...args: any[]): Promise<any>; handle(event: Electron.IpcMainInvokeEvent, ...args: any[]): Promise<any>;
createConnection(options: any): Promise<any>;
closeConnection(options: any): Promise<any>;
echo(options: any): Promise<any>;
open(options: any): Promise<any>;
close(options: any): Promise<any>;
beginTransaction(options: any): Promise<any>;
commitTransaction(options: any): Promise<any>;
rollbackTransaction(options: any): Promise<any>;
isTransactionActive(options: any): Promise<any>;
getVersion(options: any): Promise<any>;
getTableList(options: any): Promise<any>;
execute(options: any): Promise<any>;
executeSet(options: any): Promise<any>;
run(options: any): Promise<any>;
query(options: any): Promise<any>;
isDBExists(options: any): Promise<any>;
isDBOpen(options: any): Promise<any>;
isDatabase(options: any): Promise<any>;
isTableExists(options: any): Promise<any>;
deleteDatabase(options: any): Promise<any>;
isJsonValid(options: any): Promise<any>;
importFromJson(options: any): Promise<any>;
exportToJson(options: any): Promise<any>;
createSyncTable(options: any): Promise<any>;
setSyncDate(options: any): Promise<any>;
getSyncDate(options: any): Promise<any>;
deleteExportedRows(options: any): Promise<any>;
addUpgradeStatement(options: any): Promise<any>;
copyFromAssets(options: any): Promise<any>;
getFromHTTPRequest(options: any): Promise<any>;
getDatabaseList(): Promise<any>;
checkConnectionsConsistency(options: any): Promise<any>;
isSecretStored(): Promise<any>;
isPassphraseValid(options: any): Promise<any>;
setEncryptionSecret(options: any): Promise<any>;
changeEncryptionSecret(options: any): Promise<any>;
clearEncryptionSecret(): Promise<any>;
isInConfigEncryption(): Promise<any>;
isDatabaseEncrypted(options: any): Promise<any>;
checkEncryptionSecret(options: any): Promise<any>;
} }
} }

7
src/types/global.d.ts

@ -4,6 +4,13 @@ import type { CapacitorSQLite } from '@capacitor-community/sqlite';
declare global { declare global {
interface Window { interface Window {
CapacitorSQLite: typeof CapacitorSQLite; CapacitorSQLite: typeof CapacitorSQLite;
electron: {
sqlite: {
isAvailable: () => Promise<boolean>;
execute: (method: string, ...args: unknown[]) => Promise<unknown>;
};
// Add other electron IPC methods as needed
};
} }
} }

4
vite.config.electron.mts

@ -39,8 +39,8 @@ export default defineConfig({
'axios/dist/node/axios.cjs' 'axios/dist/node/axios.cjs'
], ],
output: { output: {
format: 'cjs', format: 'es',
entryFileNames: '[name].js', entryFileNames: '[name].mjs',
assetFileNames: 'assets/[name].[ext]', assetFileNames: 'assets/[name].[ext]',
}, },
}, },

Loading…
Cancel
Save