const { app, BrowserWindow, ipcMain, shell } = require('electron'); const path = require('path'); const https = require('https'); const http = require('http'); function createWindow() { const win = new BrowserWindow({ width: 1200, height: 800, webPreferences: { nodeIntegration: false, contextIsolation: true, preload: path.join(__dirname, 'preload.cjs'), sandbox: false } }); const startUrl = process.env.ELECTRON_START_URL || `file://${path.join(__dirname, '../dist/index.html')}`; if (process.env.npm_lifecycle_event === 'electron:dev') { win.loadURL('http://localhost:5173'); win.webContents.openDevTools(); } else { win.loadURL(startUrl); } } app.whenReady().then(() => { createWindow(); // Helper to fetch metadata (Zero-Knowledge: Client fetches previews) ipcMain.handle('fetch-metadata', async (event, url) => { return new Promise((resolve) => { const client = url.startsWith('https') ? https : http; const req = client.get(url, (res) => { let data = ''; res.on('data', (chunk) => data += chunk); res.on('end', () => { // Simple Regex Parser for OG Tags to avoid dependencies const getMeta = (prop) => { const regex = new RegExp(` { const regex = /(.*?)<\/title>/i; const match = data.match(regex); return match ? match[1] : null; }; const metadata = { title: getMeta('title') || getTitle(), description: getMeta('description'), image: getMeta('image'), siteName: getMeta('site_name'), themeColor: getMeta('theme-color') }; resolve(metadata); }); }); req.on('error', (err) => { console.error('Metadata fetch error:', err); resolve(null); }); }); }); ipcMain.handle('open-external', async (event, url) => { await shell.openExternal(url); }); // Crypto Handlers const crypto = require('crypto'); ipcMain.handle('generate-keys', async () => { const generateRSA = () => new Promise((resolve, reject) => { crypto.generateKeyPair('rsa', { modulusLength: 2048, publicKeyEncoding: { type: 'spki', format: 'pem' }, privateKeyEncoding: { type: 'pkcs8', format: 'pem' } }, (err, pub, priv) => { /* args are (err, publicKey, privateKey) */ if (err) reject(err); else resolve({ pub, priv }); }); }); const generateEd = () => new Promise((resolve, reject) => { crypto.generateKeyPair('ed25519', { publicKeyEncoding: { type: 'spki', format: 'pem' }, privateKeyEncoding: { type: 'pkcs8', format: 'pem' } }, (err, pub, priv) => { if (err) reject(err); else resolve({ pub, priv }); }); }); const [rsa, ed] = await Promise.all([generateRSA(), generateEd()]); return { rsaPub: rsa.pub, rsaPriv: rsa.priv, edPub: ed.pub, edPriv: ed.priv }; }); ipcMain.handle('random-bytes', (event, size) => { return crypto.randomBytes(size).toString('hex'); }); ipcMain.handle('sha256', (event, data) => { return crypto.createHash('sha256').update(data).digest('hex'); }); ipcMain.handle('derive-auth-keys', (event, password, salt) => { return new Promise((resolve, reject) => { crypto.scrypt(password, salt, 64, (err, derivedKey) => { if (err) reject(err); else { const dak = derivedKey.subarray(0, 32).toString('hex'); const dek = derivedKey.subarray(32, 64); resolve({ dak, dek }); } }); }); }); ipcMain.handle('encrypt-data', (event, plaintext, key) => { console.log('encrypt-data called with:', { plaintextType: typeof plaintext, isBuffer: Buffer.isBuffer(plaintext), keyType: typeof key, keyIsBuffer: Buffer.isBuffer(plaintext) }); if (plaintext === undefined) throw new TypeError('plaintext is undefined'); if (key === undefined) throw new TypeError('key is undefined'); // Key can be hex string or buffer. 32 bytes for AES-256 const keyBuffer = typeof key === 'string' ? Buffer.from(key, 'hex') : key; const iv = crypto.randomBytes(12); // 96-bit IV for GCM const cipher = crypto.createCipheriv('aes-256-gcm', keyBuffer, iv); // Use Buffer concatenation to handle both string and Buffer inputs correctly without encoding confusion const encryptedBuffer = Buffer.concat([ cipher.update(plaintext), cipher.final() ]); const tag = cipher.getAuthTag().toString('hex'); return { content: encryptedBuffer.toString('hex'), iv: iv.toString('hex'), tag }; }); ipcMain.handle('decrypt-data', (event, ciphertext, key, iv, tag) => { const keyBuffer = typeof key === 'string' ? Buffer.from(key, 'hex') : key; const ivBuffer = Buffer.from(iv, 'hex'); const tagBuffer = Buffer.from(tag, 'hex'); const decipher = crypto.createDecipheriv('aes-256-gcm', keyBuffer, ivBuffer); decipher.setAuthTag(tagBuffer); let decrypted = decipher.update(ciphertext, 'hex', 'utf8'); decrypted += decipher.final('utf8'); return decrypted; }); });