desktop: add Electron app scaffold (UI + preload bridge)
This commit is contained in:
parent
b49225b0a5
commit
40aa19391d
6 changed files with 168 additions and 0 deletions
27
electron/main.js
Normal file
27
electron/main.js
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
const { app, BrowserWindow } = require('electron');
|
||||
const path = require('path');
|
||||
|
||||
function createWindow() {
|
||||
const win = new BrowserWindow({
|
||||
width: 900,
|
||||
height: 700,
|
||||
webPreferences: {
|
||||
preload: path.join(__dirname, 'preload.js'),
|
||||
contextIsolation: true,
|
||||
nodeIntegration: false
|
||||
}
|
||||
});
|
||||
|
||||
win.loadFile(path.join(__dirname, 'renderer', 'index.html'));
|
||||
}
|
||||
|
||||
app.whenReady().then(() => {
|
||||
createWindow();
|
||||
app.on('activate', function () {
|
||||
if (BrowserWindow.getAllWindows().length === 0) createWindow();
|
||||
});
|
||||
});
|
||||
|
||||
app.on('window-all-closed', function () {
|
||||
if (process.platform !== 'darwin') app.quit();
|
||||
});
|
||||
13
electron/package.json
Normal file
13
electron/package.json
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
{
|
||||
"name": "cryptec-electron",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"main": "main.js",
|
||||
"scripts": {
|
||||
"start": "electron .",
|
||||
"test": "node ./test/electron-smoke.js"
|
||||
},
|
||||
"devDependencies": {
|
||||
"electron": "^25.0.0"
|
||||
}
|
||||
}
|
||||
56
electron/preload.js
Normal file
56
electron/preload.js
Normal file
|
|
@ -0,0 +1,56 @@
|
|||
const { contextBridge } = require('electron');
|
||||
|
||||
let nativeBindings = null;
|
||||
try {
|
||||
// Try to require the Node bindings package (napi). If not available, fall back to a JS shim.
|
||||
nativeBindings = require(pathJoin(__dirname, '..', 'bindings'));
|
||||
} catch (e) {
|
||||
// ignore
|
||||
}
|
||||
|
||||
function pathJoin(...parts) {
|
||||
return parts.join('/');
|
||||
}
|
||||
|
||||
const shim = {
|
||||
generate_mnemonic: (strength = 128) => {
|
||||
// simple in-process fallback
|
||||
const crypto = require('crypto');
|
||||
// use 128-bit entropy => 12 words via bip39 library if available
|
||||
try {
|
||||
const bip39 = require('bip39');
|
||||
return bip39.generateMnemonic(strength);
|
||||
} catch (e) {
|
||||
return crypto.randomBytes(16).toString('hex');
|
||||
}
|
||||
},
|
||||
first_receive_address: (mnemonic) => {
|
||||
try {
|
||||
const bip39 = require('bip39');
|
||||
const bitcoin = require('bitcoinjs-lib');
|
||||
const seed = bip39.mnemonicToSeedSync(mnemonic);
|
||||
const root = bitcoin.bip32.fromSeed(seed, bitcoin.networks.testnet);
|
||||
const child = root.derivePath("m/84'/1'/0'/0/0");
|
||||
const { address } = bitcoin.payments.p2wpkh({ pubkey: child.publicKey, network: bitcoin.networks.testnet });
|
||||
return address || '';
|
||||
} catch (e) {
|
||||
return '';
|
||||
}
|
||||
},
|
||||
create_psbt: (_mnemonic, toAddress, satoshis) => {
|
||||
return Buffer.from(JSON.stringify({ type: 'psbt-mock', to: toAddress, sats: satoshis })).toString('base64');
|
||||
},
|
||||
sign_psbt: (_mnemonic, psbtB64) => {
|
||||
const decoded = Buffer.from(psbtB64, 'base64').toString('utf8');
|
||||
return Buffer.from(JSON.stringify({ signed: true, original: decoded })).toString('base64');
|
||||
}
|
||||
};
|
||||
|
||||
const api = nativeBindings ? nativeBindings : shim;
|
||||
|
||||
contextBridge.exposeInMainWorld('cryptec', {
|
||||
generateMnemonic: (strength) => api.generate_mnemonic ? api.generate_mnemonic(strength) : api.generate_mnemonic(strength),
|
||||
firstReceiveAddress: (mnemonic) => api.first_receive_address ? api.first_receive_address(mnemonic) : api.first_receive_address(mnemonic),
|
||||
createPsbt: (mnemonic, to, sats) => api.create_psbt ? api.create_psbt(mnemonic, to, sats) : api.create_psbt(mnemonic, to, sats),
|
||||
signPsbt: (mnemonic, psbtB64) => api.sign_psbt ? api.sign_psbt(mnemonic, psbtB64) : api.sign_psbt(mnemonic, psbtB64)
|
||||
});
|
||||
39
electron/renderer/index.html
Normal file
39
electron/renderer/index.html
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<title>CrypteCipher Desktop</title>
|
||||
<meta http-equiv="Content-Security-Policy" content="default-src 'self' 'unsafe-inline' data:;" />
|
||||
<style>
|
||||
body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial; margin: 0; padding: 20px; }
|
||||
.container { max-width: 820px; margin: 0 auto; }
|
||||
.addr { background: #f6f6f6; padding: 12px; border-radius: 6px; font-family: monospace; }
|
||||
.section { margin-top: 18px; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<h1>CrypteCipher — Desktop (Electron)</h1>
|
||||
<div>
|
||||
<button id="new">Create / Generate mnemonic</button>
|
||||
<button id="addr">Get first receive address</button>
|
||||
</div>
|
||||
<div class="section">
|
||||
<h3>Mnemonic</h3>
|
||||
<div id="mn" class="addr">(none)</div>
|
||||
</div>
|
||||
<div class="section">
|
||||
<h3>Receive Address</h3>
|
||||
<div id="addrbox" class="addr">(none)</div>
|
||||
</div>
|
||||
<div class="section">
|
||||
<h3>Send (mock)</h3>
|
||||
<input id="to" placeholder="to address" style="width:60%" />
|
||||
<input id="sats" placeholder="satoshis" style="width:20%" />
|
||||
<button id="psbt">Create PSBT</button>
|
||||
</div>
|
||||
<pre id="out"></pre>
|
||||
</div>
|
||||
<script src="./renderer.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
21
electron/renderer/renderer.js
Normal file
21
electron/renderer/renderer.js
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
const $ = (id) => document.getElementById(id);
|
||||
|
||||
$('new').addEventListener('click', async () => {
|
||||
const m = await window.cryptec.generateMnemonic(128);
|
||||
$('mn').innerText = m;
|
||||
});
|
||||
|
||||
$('addr').addEventListener('click', () => {
|
||||
const mn = $('mn').innerText;
|
||||
if (!mn) return alert('generate or enter mnemonic first');
|
||||
const a = window.cryptec.firstReceiveAddress(mn);
|
||||
$('addrbox').innerText = a;
|
||||
});
|
||||
|
||||
$('psbt').addEventListener('click', async () => {
|
||||
const mn = $('mn').innerText;
|
||||
const to = $('to').value || 'tb1qexampleaddress';
|
||||
const sats = parseInt($('sats').value || '1000', 10);
|
||||
const psbt = await window.cryptec.createPsbt(mn, to, sats);
|
||||
$('out').innerText = psbt;
|
||||
});
|
||||
12
electron/test/electron-smoke.js
Normal file
12
electron/test/electron-smoke.js
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
const { execSync } = require('child_process');
|
||||
const path = require('path');
|
||||
try {
|
||||
// smoke: ensure electron module can be required
|
||||
const electron = require('electron');
|
||||
console.log('electron available:', !!electron);
|
||||
console.log('Electron smoke test passed');
|
||||
process.exit(0);
|
||||
} catch (e) {
|
||||
console.error('Electron smoke test failed', e.message);
|
||||
process.exit(1);
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue