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. 144
      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-plugin-pwa": "^1.0.0"
},
"main": "./dist-electron/main.js",
"main": "./dist-electron/main.mjs",
"build": {
"appId": "app.timesafari",
"productName": "TimeSafari",
@ -190,8 +190,8 @@
"to": "www"
},
{
"from": "dist-electron/resources/preload.js",
"to": "preload.js"
"from": "dist-electron/resources/preload.mjs",
"to": "preload.mjs"
}
],
"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);
// Copy preload script to resources
const preloadSrc = path.join(electronDistPath, "preload.js");
const preloadDest = path.join(electronDistPath, "resources", "preload.js");
const preloadSrc = path.join(electronDistPath, "preload.mjs");
const preloadDest = path.join(electronDistPath, "resources", "preload.mjs");
// Ensure resources directory exists
const resourcesDir = path.join(electronDistPath, "resources");

144
src/electron/main.ts

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

15
src/libs/endorserServer.ts

@ -685,6 +685,7 @@ export function hydrateGive(
if (amount && !isNaN(amount)) {
const quantitativeValue: QuantitativeValue = {
"@type": "QuantitativeValue",
amountOfThisGood: amount,
unitCode: unitCode || "HUR",
};
@ -870,7 +871,6 @@ export function hydrateOffer(
if (amount && !isNaN(amount)) {
const quantitativeValue: QuantitativeValue = {
"@context": SCHEMA_ORG_CONTEXT,
"@type": "QuantitativeValue",
amountOfThisGood: amount,
unitCode: unitCode || "HUR",
@ -997,11 +997,12 @@ export async function createAndSubmitClaim(
axios: Axios,
): Promise<CreateAndSubmitClaimResult> {
try {
const vcPayload = {
const vcPayload: { vc: VerifiableCredentialClaim } = {
vc: {
"@context": ["https://www.w3.org/2018/credentials/v1"],
"@context": "https://www.w3.org/2018/credentials/v1",
"@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
const vcPayload = {
vc: {
"@context": ["https://www.w3.org/2018/credentials/v1"],
"@context": "https://www.w3.org/2018/credentials/v1",
type: ["VerifiableCredential"],
credentialSubject: claim,
},
@ -1332,10 +1333,10 @@ export async function createInviteJwt(
// Make a payload for the claim
const vcPayload: { vc: VerifiableCredentialClaim } = {
vc: {
"@context": ["https://www.w3.org/2018/credentials/v1"],
"@context": "https://www.w3.org/2018/credentials/v1",
"@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 { logger } from "./utils/logger";
import { CapacitorSQLite } from "@capacitor-community/sqlite";
// Initialize SQLite plugin for Electron
if (typeof window !== "undefined") {
// Register the plugin globally
window.CapacitorSQLite = CapacitorSQLite;
logger.info("[Electron] SQLite plugin initialized in native mode");
async function initializeSQLite() {
try {
const isAvailable = await window.electron.sqlite.isAvailable();
if (!isAvailable) {
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;
@ -20,5 +25,10 @@ if (pwa_enabled) {
logger.warn("[Electron] PWA is enabled, but not supported in electron");
}
// Initialize app and SQLite
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;
constructor() {
// Ensure we're using the native implementation
// Use the IPC bridge for SQLite operations
if (!window.CapacitorSQLite) {
throw new Error("CapacitorSQLite not initialized in Electron");
}
@ -45,6 +45,12 @@ export class ElectronPlatformService implements PlatformService {
}
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
this.db = await this.sqlite.createConnection(
this.dbName,

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

@ -1,6 +1,46 @@
declare module '@capacitor-community/sqlite/electron/dist/plugin' {
export class CapacitorSQLiteElectron {
declare module '@capacitor-community/sqlite/electron/dist/plugin.js' {
export class CapacitorSQLite {
constructor();
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 {
interface Window {
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'
],
output: {
format: 'cjs',
entryFileNames: '[name].js',
format: 'es',
entryFileNames: '[name].mjs',
assetFileNames: 'assets/[name].[ext]',
},
},

Loading…
Cancel
Save