801 lines
25 KiB
TypeScript
801 lines
25 KiB
TypeScript
import { app, BrowserWindow, ipcMain, shell, dialog } from 'electron';
|
||
import * as path from 'path';
|
||
import * as fs from 'fs';
|
||
import express from 'express';
|
||
import { GrpcClient } from './modules/grpc-client';
|
||
import { DatabaseManager } from './modules/database';
|
||
import { addressDerivationService, CHAIN_CONFIGS } from './modules/address-derivation';
|
||
import { KavaTxService, KAVA_MAINNET_TX_CONFIG } from './modules/kava-tx-service';
|
||
import { AccountClient } from './modules/account-client';
|
||
|
||
// 内置 HTTP 服务器端口
|
||
const HTTP_PORT = 3456;
|
||
|
||
let mainWindow: BrowserWindow | null = null;
|
||
let grpcClient: GrpcClient | null = null;
|
||
let database: DatabaseManager | null = null;
|
||
let httpServer: ReturnType<typeof express.application.listen> | null = null;
|
||
let kavaTxService: KavaTxService | null = null;
|
||
let accountClient: AccountClient | null = null;
|
||
|
||
// 创建主窗口
|
||
function createWindow() {
|
||
mainWindow = new BrowserWindow({
|
||
width: 1200,
|
||
height: 800,
|
||
minWidth: 800,
|
||
minHeight: 600,
|
||
webPreferences: {
|
||
preload: path.join(__dirname, 'preload.js'),
|
||
contextIsolation: true,
|
||
nodeIntegration: false,
|
||
},
|
||
titleBarStyle: 'hiddenInset',
|
||
show: false,
|
||
});
|
||
|
||
// 开发模式下加载 Vite 开发服务器
|
||
if (process.env.NODE_ENV === 'development') {
|
||
mainWindow.loadURL('http://localhost:5173');
|
||
mainWindow.webContents.openDevTools();
|
||
} else {
|
||
// 生产模式下加载打包后的文件
|
||
mainWindow.loadFile(path.join(__dirname, '../dist/index.html'));
|
||
}
|
||
|
||
mainWindow.once('ready-to-show', () => {
|
||
mainWindow?.show();
|
||
});
|
||
|
||
mainWindow.on('closed', () => {
|
||
mainWindow = null;
|
||
});
|
||
|
||
// 处理外部链接
|
||
mainWindow.webContents.setWindowOpenHandler(({ url }) => {
|
||
shell.openExternal(url);
|
||
return { action: 'deny' };
|
||
});
|
||
}
|
||
|
||
// 启动内置 HTTP 服务器
|
||
function startHttpServer() {
|
||
const expressApp = express();
|
||
|
||
expressApp.use(express.json());
|
||
expressApp.use(express.static(path.join(__dirname, '../dist')));
|
||
|
||
// API 路由
|
||
expressApp.get('/api/status', (_req, res) => {
|
||
res.json({
|
||
connected: grpcClient?.isConnected() ?? false,
|
||
partyId: grpcClient?.getPartyId() ?? null,
|
||
});
|
||
});
|
||
|
||
// 所有其他路由返回 index.html (SPA)
|
||
expressApp.get('*', (_req, res) => {
|
||
res.sendFile(path.join(__dirname, '../dist/index.html'));
|
||
});
|
||
|
||
httpServer = expressApp.listen(HTTP_PORT, '127.0.0.1', () => {
|
||
console.log(`HTTP server running at http://127.0.0.1:${HTTP_PORT}`);
|
||
});
|
||
}
|
||
|
||
// 初始化服务
|
||
async function initServices() {
|
||
// 初始化 gRPC 客户端
|
||
grpcClient = new GrpcClient();
|
||
|
||
// 初始化数据库
|
||
database = new DatabaseManager();
|
||
|
||
// 初始化 Kava 交易服务
|
||
kavaTxService = new KavaTxService(KAVA_MAINNET_TX_CONFIG);
|
||
|
||
// 初始化 Account 服务 HTTP 客户端
|
||
// 从数据库读取 Account 服务 URL,默认使用生产环境地址
|
||
const settings = database.getAllSettings();
|
||
const accountServiceUrl = settings['account_service_url'] || 'https://api.szaiai.com';
|
||
accountClient = new AccountClient(accountServiceUrl);
|
||
|
||
// 设置 IPC 处理器
|
||
setupIpcHandlers();
|
||
}
|
||
|
||
// 设置 IPC 通信处理器
|
||
function setupIpcHandlers() {
|
||
// ===========================================================================
|
||
// gRPC 相关
|
||
// ===========================================================================
|
||
|
||
// gRPC 连接
|
||
ipcMain.handle('grpc:connect', async (_event, { host, port }) => {
|
||
try {
|
||
await grpcClient?.connect(host, port);
|
||
return { success: true };
|
||
} catch (error) {
|
||
return { success: false, error: (error as Error).message };
|
||
}
|
||
});
|
||
|
||
// 注册为参与方
|
||
ipcMain.handle('grpc:register', async (_event, { partyId, role }) => {
|
||
try {
|
||
await grpcClient?.registerParty(partyId, role);
|
||
return { success: true };
|
||
} catch (error) {
|
||
return { success: false, error: (error as Error).message };
|
||
}
|
||
});
|
||
|
||
// 加入会话
|
||
ipcMain.handle('grpc:joinSession', async (_event, { sessionId, partyId, joinToken }) => {
|
||
try {
|
||
const result = await grpcClient?.joinSession(sessionId, partyId, joinToken);
|
||
return { success: true, data: result };
|
||
} catch (error) {
|
||
return { success: false, error: (error as Error).message };
|
||
}
|
||
});
|
||
|
||
// gRPC - 创建会话 (通过 Account 服务 HTTP API)
|
||
ipcMain.handle('grpc:createSession', async (_event, params) => {
|
||
try {
|
||
const result = await accountClient?.createKeygenSession({
|
||
wallet_name: params.walletName,
|
||
threshold_t: params.thresholdT,
|
||
threshold_n: params.thresholdN,
|
||
persistent_count: 0, // 服务端 party 数量,共管钱包模式下为 0
|
||
external_count: params.thresholdN, // 所有参与方都是外部 party
|
||
expires_in_seconds: 86400, // 24 小时
|
||
});
|
||
return {
|
||
success: true,
|
||
sessionId: result?.session_id,
|
||
inviteCode: result?.invite_code,
|
||
walletName: result?.wallet_name,
|
||
expiresAt: result?.expires_at,
|
||
joinTokens: result?.join_tokens,
|
||
};
|
||
} catch (error) {
|
||
return { success: false, error: (error as Error).message };
|
||
}
|
||
});
|
||
|
||
// gRPC - 验证邀请码 (通过 Account 服务 HTTP API)
|
||
ipcMain.handle('grpc:validateInviteCode', async (_event, { code }) => {
|
||
try {
|
||
const result = await accountClient?.getSessionByInviteCode(code);
|
||
return {
|
||
success: true,
|
||
sessionInfo: {
|
||
sessionId: result?.session_id,
|
||
walletName: result?.wallet_name,
|
||
threshold: {
|
||
t: result?.threshold_t,
|
||
n: result?.threshold_n,
|
||
},
|
||
status: result?.status,
|
||
currentParticipants: result?.joined_parties || 0,
|
||
},
|
||
};
|
||
} catch (error) {
|
||
return { success: false, error: (error as Error).message };
|
||
}
|
||
});
|
||
|
||
// gRPC - 获取会话状态 (通过 Account 服务 HTTP API)
|
||
ipcMain.handle('grpc:getSessionStatus', async (_event, { sessionId }) => {
|
||
try {
|
||
const result = await accountClient?.getSessionStatus(sessionId);
|
||
return {
|
||
success: true,
|
||
session: {
|
||
sessionId: result?.session_id,
|
||
status: result?.status,
|
||
completedParties: result?.completed_parties,
|
||
totalParties: result?.total_parties,
|
||
sessionType: result?.session_type,
|
||
publicKey: result?.public_key,
|
||
},
|
||
};
|
||
} catch (error) {
|
||
return { success: false, error: (error as Error).message };
|
||
}
|
||
});
|
||
|
||
// gRPC - 测试连接
|
||
ipcMain.handle('grpc:testConnection', async (_event, { url }) => {
|
||
try {
|
||
// 地址格式: host:port (例如 mpc-grpc.szaiai.com:443)
|
||
await grpcClient?.connect(url);
|
||
return { success: true };
|
||
} catch (error) {
|
||
return { success: false, error: (error as Error).message };
|
||
}
|
||
});
|
||
|
||
// gRPC - 验证签名会话 (通过 Account 服务 HTTP API)
|
||
ipcMain.handle('grpc:validateSigningSession', async (_event, { code }) => {
|
||
try {
|
||
const result = await accountClient?.getSignSessionByInviteCode(code);
|
||
return {
|
||
success: true,
|
||
session: {
|
||
sessionId: result?.session_id,
|
||
keygenSessionId: result?.keygen_session_id,
|
||
walletName: result?.wallet_name,
|
||
messageHash: result?.message_hash,
|
||
threshold: {
|
||
t: result?.threshold_t,
|
||
n: result?.parties?.length || 0,
|
||
},
|
||
currentParticipants: result?.joined_count || 0,
|
||
status: result?.status,
|
||
parties: result?.parties,
|
||
},
|
||
};
|
||
} catch (error) {
|
||
return { success: false, error: (error as Error).message };
|
||
}
|
||
});
|
||
|
||
// gRPC - 加入签名会话
|
||
ipcMain.handle('grpc:joinSigningSession', async (_event, params) => {
|
||
try {
|
||
// 从本地 SQLite 获取 share 数据
|
||
const share = database?.getShare(params.shareId, params.password);
|
||
if (!share) {
|
||
return { success: false, error: 'Share not found or incorrect password' };
|
||
}
|
||
|
||
// 加入签名会话 (通过 gRPC)
|
||
// TODO: 实际加入会话逻辑需要使用 gRPC client
|
||
// 这里先返回成功,表示验证通过
|
||
return {
|
||
success: true,
|
||
partyId: share.party_id,
|
||
partyIndex: share.party_index,
|
||
};
|
||
} catch (error) {
|
||
return { success: false, error: (error as Error).message };
|
||
}
|
||
});
|
||
|
||
// ===========================================================================
|
||
// Account 服务相关 (HTTP API)
|
||
// ===========================================================================
|
||
|
||
// 创建签名会话
|
||
ipcMain.handle('account:createSignSession', async (_event, params) => {
|
||
try {
|
||
// 从本地 SQLite 获取 share 数据
|
||
const share = database?.getShare(params.shareId, params.password);
|
||
if (!share) {
|
||
return { success: false, error: 'Share not found or incorrect password' };
|
||
}
|
||
|
||
// 解析 participants_json 获取参与方列表
|
||
const participants = JSON.parse(share.participants_json || '[]');
|
||
const parties = participants.map((p: { partyId: string }, index: number) => ({
|
||
party_id: p.partyId,
|
||
party_index: index,
|
||
}));
|
||
|
||
const result = await accountClient?.createSignSession({
|
||
keygen_session_id: share.session_id,
|
||
wallet_name: share.wallet_name,
|
||
message_hash: params.messageHash,
|
||
parties: parties,
|
||
threshold_t: share.threshold_t,
|
||
initiator_name: params.initiatorName,
|
||
});
|
||
|
||
return {
|
||
success: true,
|
||
sessionId: result?.session_id,
|
||
inviteCode: result?.invite_code,
|
||
expiresAt: result?.expires_at,
|
||
joinToken: result?.join_token,
|
||
};
|
||
} catch (error) {
|
||
return { success: false, error: (error as Error).message };
|
||
}
|
||
});
|
||
|
||
// 获取签名会话状态 (通过邀请码)
|
||
ipcMain.handle('account:getSignSessionByInviteCode', async (_event, { inviteCode }) => {
|
||
try {
|
||
const result = await accountClient?.getSignSessionByInviteCode(inviteCode);
|
||
return {
|
||
success: true,
|
||
session: {
|
||
sessionId: result?.session_id,
|
||
keygenSessionId: result?.keygen_session_id,
|
||
walletName: result?.wallet_name,
|
||
messageHash: result?.message_hash,
|
||
thresholdT: result?.threshold_t,
|
||
status: result?.status,
|
||
inviteCode: result?.invite_code,
|
||
expiresAt: result?.expires_at,
|
||
parties: result?.parties,
|
||
joinedCount: result?.joined_count,
|
||
},
|
||
};
|
||
} catch (error) {
|
||
return { success: false, error: (error as Error).message };
|
||
}
|
||
});
|
||
|
||
// Account 服务健康检查
|
||
ipcMain.handle('account:healthCheck', async () => {
|
||
try {
|
||
const result = await accountClient?.healthCheck();
|
||
return { success: true, data: result };
|
||
} catch (error) {
|
||
return { success: false, error: (error as Error).message };
|
||
}
|
||
});
|
||
|
||
// 测试 Account 服务连接
|
||
ipcMain.handle('account:testConnection', async () => {
|
||
try {
|
||
const connected = await accountClient?.testConnection();
|
||
return { success: connected };
|
||
} catch (error) {
|
||
return { success: false, error: (error as Error).message };
|
||
}
|
||
});
|
||
|
||
// 更新 Account 服务 URL
|
||
ipcMain.handle('account:updateUrl', async (_event, { url }) => {
|
||
try {
|
||
accountClient?.setBaseUrl(url);
|
||
database?.setSetting('account_service_url', url);
|
||
return { success: true };
|
||
} catch (error) {
|
||
return { success: false, error: (error as Error).message };
|
||
}
|
||
});
|
||
|
||
// 获取 Account 服务 URL
|
||
ipcMain.handle('account:getUrl', async () => {
|
||
return accountClient?.getBaseUrl() || 'https://api.szaiai.com';
|
||
});
|
||
|
||
// ===========================================================================
|
||
// Share 存储相关 (SQLite)
|
||
// ===========================================================================
|
||
|
||
// 保存 share
|
||
ipcMain.handle('storage:saveShare', async (_event, { share, password }) => {
|
||
try {
|
||
const saved = database?.saveShare({
|
||
sessionId: share.sessionId,
|
||
walletName: share.walletName,
|
||
partyId: share.partyId,
|
||
partyIndex: share.partyIndex,
|
||
thresholdT: share.threshold.t,
|
||
thresholdN: share.threshold.n,
|
||
publicKeyHex: share.publicKey,
|
||
rawShare: share.rawShare,
|
||
participants: share.participants || [],
|
||
}, password);
|
||
|
||
// 自动派生 Kava 地址
|
||
if (saved && share.publicKey) {
|
||
try {
|
||
const kavaAddress = addressDerivationService.deriveAddress(share.publicKey, 'kava');
|
||
database?.saveDerivedAddress({
|
||
shareId: saved.id,
|
||
chain: 'kava',
|
||
derivationPath: kavaAddress.derivationPath,
|
||
address: kavaAddress.address,
|
||
publicKeyHex: share.publicKey,
|
||
});
|
||
} catch (err) {
|
||
console.error('Failed to derive Kava address:', err);
|
||
}
|
||
}
|
||
|
||
return { success: true, data: saved };
|
||
} catch (error) {
|
||
return { success: false, error: (error as Error).message };
|
||
}
|
||
});
|
||
|
||
// 获取 share 列表
|
||
ipcMain.handle('storage:listShares', async () => {
|
||
try {
|
||
const shares = database?.listShares() ?? [];
|
||
// 转换为前端期望的格式
|
||
const formatted = shares.map(share => ({
|
||
id: share.id,
|
||
sessionId: share.session_id,
|
||
walletName: share.wallet_name,
|
||
partyId: share.party_id,
|
||
partyIndex: share.party_index,
|
||
threshold: {
|
||
t: share.threshold_t,
|
||
n: share.threshold_n,
|
||
},
|
||
publicKey: share.public_key_hex,
|
||
createdAt: share.created_at,
|
||
lastUsedAt: share.last_used_at,
|
||
participants: JSON.parse(share.participants_json || '[]'),
|
||
}));
|
||
return { success: true, data: formatted };
|
||
} catch (error) {
|
||
return { success: false, error: (error as Error).message };
|
||
}
|
||
});
|
||
|
||
// 获取单个 share (解密)
|
||
ipcMain.handle('storage:getShare', async (_event, { id, password }) => {
|
||
try {
|
||
const share = database?.getShare(id, password);
|
||
if (!share) return null;
|
||
|
||
return {
|
||
id: share.id,
|
||
sessionId: share.session_id,
|
||
walletName: share.wallet_name,
|
||
partyId: share.party_id,
|
||
partyIndex: share.party_index,
|
||
threshold: {
|
||
t: share.threshold_t,
|
||
n: share.threshold_n,
|
||
},
|
||
publicKey: share.public_key_hex,
|
||
rawShare: share.raw_share,
|
||
createdAt: share.created_at,
|
||
lastUsedAt: share.last_used_at,
|
||
participants: JSON.parse(share.participants_json || '[]'),
|
||
};
|
||
} catch (error) {
|
||
return null;
|
||
}
|
||
});
|
||
|
||
// 删除 share
|
||
ipcMain.handle('storage:deleteShare', async (_event, { id }) => {
|
||
try {
|
||
database?.deleteShare(id);
|
||
return { success: true };
|
||
} catch (error) {
|
||
return { success: false, error: (error as Error).message };
|
||
}
|
||
});
|
||
|
||
// 导出 share
|
||
ipcMain.handle('storage:exportShare', async (_event, { id, password }) => {
|
||
try {
|
||
const data = database?.exportShare(id, password);
|
||
return { success: true, data };
|
||
} catch (error) {
|
||
return { success: false, error: (error as Error).message };
|
||
}
|
||
});
|
||
|
||
// 导入 share
|
||
ipcMain.handle('storage:importShare', async (_event, { filePath, password }) => {
|
||
try {
|
||
const data = fs.readFileSync(filePath);
|
||
const share = database?.importShare(data, password);
|
||
return { success: true, share };
|
||
} catch (error) {
|
||
return { success: false, error: (error as Error).message };
|
||
}
|
||
});
|
||
|
||
// ===========================================================================
|
||
// 地址派生相关
|
||
// ===========================================================================
|
||
|
||
// 派生地址
|
||
ipcMain.handle('address:derive', async (_event, { shareId, chain, password }) => {
|
||
try {
|
||
const share = database?.getShare(shareId, password);
|
||
if (!share) {
|
||
return { success: false, error: 'Share not found' };
|
||
}
|
||
|
||
const derived = addressDerivationService.deriveAddress(share.public_key_hex, chain);
|
||
|
||
// 保存到数据库
|
||
const saved = database?.saveDerivedAddress({
|
||
shareId,
|
||
chain,
|
||
derivationPath: derived.derivationPath,
|
||
address: derived.address,
|
||
publicKeyHex: share.public_key_hex,
|
||
});
|
||
|
||
return { success: true, data: { ...derived, id: saved?.id } };
|
||
} catch (error) {
|
||
return { success: false, error: (error as Error).message };
|
||
}
|
||
});
|
||
|
||
// 派生所有支持的链地址
|
||
ipcMain.handle('address:deriveAll', async (_event, { shareId, password }) => {
|
||
try {
|
||
const share = database?.getShare(shareId, password);
|
||
if (!share) {
|
||
return { success: false, error: 'Share not found' };
|
||
}
|
||
|
||
const addresses = addressDerivationService.deriveAllAddresses(share.public_key_hex);
|
||
|
||
// 保存所有地址
|
||
for (const addr of addresses) {
|
||
database?.saveDerivedAddress({
|
||
shareId,
|
||
chain: addr.chain,
|
||
derivationPath: addr.derivationPath,
|
||
address: addr.address,
|
||
publicKeyHex: share.public_key_hex,
|
||
});
|
||
}
|
||
|
||
return { success: true, data: addresses };
|
||
} catch (error) {
|
||
return { success: false, error: (error as Error).message };
|
||
}
|
||
});
|
||
|
||
// 获取 share 的所有派生地址
|
||
ipcMain.handle('address:list', async (_event, { shareId }) => {
|
||
try {
|
||
const addresses = database?.getAddressesByShare(shareId) ?? [];
|
||
return {
|
||
success: true,
|
||
data: addresses.map(addr => ({
|
||
id: addr.id,
|
||
shareId: addr.share_id,
|
||
chain: addr.chain,
|
||
chainName: CHAIN_CONFIGS[addr.chain]?.name || addr.chain,
|
||
derivationPath: addr.derivation_path,
|
||
address: addr.address,
|
||
publicKeyHex: addr.public_key_hex,
|
||
createdAt: addr.created_at,
|
||
})),
|
||
};
|
||
} catch (error) {
|
||
return { success: false, error: (error as Error).message };
|
||
}
|
||
});
|
||
|
||
// 获取支持的链列表
|
||
ipcMain.handle('address:getSupportedChains', async () => {
|
||
return addressDerivationService.getSupportedChains();
|
||
});
|
||
|
||
// ===========================================================================
|
||
// 签名历史相关
|
||
// ===========================================================================
|
||
|
||
// 获取签名历史
|
||
ipcMain.handle('signing:getHistory', async (_event, { shareId }) => {
|
||
try {
|
||
const history = database?.getSigningHistoryByShare(shareId) ?? [];
|
||
return {
|
||
success: true,
|
||
data: history.map(h => ({
|
||
id: h.id,
|
||
shareId: h.share_id,
|
||
sessionId: h.session_id,
|
||
messageHash: h.message_hash,
|
||
signature: h.signature,
|
||
status: h.status,
|
||
errorMessage: h.error_message,
|
||
createdAt: h.created_at,
|
||
completedAt: h.completed_at,
|
||
})),
|
||
};
|
||
} catch (error) {
|
||
return { success: false, error: (error as Error).message };
|
||
}
|
||
});
|
||
|
||
// ===========================================================================
|
||
// 设置相关
|
||
// ===========================================================================
|
||
|
||
// 获取设置
|
||
ipcMain.handle('storage:getSettings', async () => {
|
||
try {
|
||
const settings = database?.getAllSettings() ?? {};
|
||
return {
|
||
messageRouterUrl: settings['message_router_url'] || 'mpc-grpc.szaiai.com:443',
|
||
autoBackup: settings['auto_backup'] === 'true',
|
||
backupPath: settings['backup_path'] || '',
|
||
};
|
||
} catch (error) {
|
||
return null;
|
||
}
|
||
});
|
||
|
||
// 保存设置
|
||
ipcMain.handle('storage:saveSettings', async (_event, { settings }) => {
|
||
try {
|
||
if (settings.messageRouterUrl !== undefined) {
|
||
database?.setSetting('message_router_url', settings.messageRouterUrl);
|
||
}
|
||
if (settings.autoBackup !== undefined) {
|
||
database?.setSetting('auto_backup', settings.autoBackup ? 'true' : 'false');
|
||
}
|
||
if (settings.backupPath !== undefined) {
|
||
database?.setSetting('backup_path', settings.backupPath);
|
||
}
|
||
return { success: true };
|
||
} catch (error) {
|
||
return { success: false, error: (error as Error).message };
|
||
}
|
||
});
|
||
|
||
// ===========================================================================
|
||
// Kava 区块链相关
|
||
// ===========================================================================
|
||
|
||
// 查询 Kava 余额
|
||
ipcMain.handle('kava:getBalance', async (_event, { address }) => {
|
||
try {
|
||
const balance = await kavaTxService?.getKavaBalance(address);
|
||
return { success: true, data: balance };
|
||
} catch (error) {
|
||
return { success: false, error: (error as Error).message };
|
||
}
|
||
});
|
||
|
||
// 查询所有余额
|
||
ipcMain.handle('kava:getAllBalances', async (_event, { address }) => {
|
||
try {
|
||
const balances = await kavaTxService?.getAllBalances(address);
|
||
return { success: true, data: balances };
|
||
} catch (error) {
|
||
return { success: false, error: (error as Error).message };
|
||
}
|
||
});
|
||
|
||
// 查询账户信息
|
||
ipcMain.handle('kava:getAccountInfo', async (_event, { address }) => {
|
||
try {
|
||
const info = await kavaTxService?.getAccountInfo(address);
|
||
return { success: true, data: info };
|
||
} catch (error) {
|
||
return { success: false, error: (error as Error).message };
|
||
}
|
||
});
|
||
|
||
// 构建转账交易 (待签名)
|
||
ipcMain.handle('kava:buildSendTx', async (_event, { fromAddress, toAddress, amount, publicKeyHex, memo }) => {
|
||
try {
|
||
const unsignedTx = await kavaTxService?.buildSendTx(
|
||
fromAddress,
|
||
toAddress,
|
||
amount,
|
||
publicKeyHex,
|
||
memo || ''
|
||
);
|
||
return { success: true, data: unsignedTx };
|
||
} catch (error) {
|
||
return { success: false, error: (error as Error).message };
|
||
}
|
||
});
|
||
|
||
// 完成签名并广播交易
|
||
ipcMain.handle('kava:completeTxAndBroadcast', async (_event, { unsignedTx, signatureHex }) => {
|
||
try {
|
||
// 完成交易签名
|
||
const signedTx = await kavaTxService?.completeTx(unsignedTx, signatureHex);
|
||
if (!signedTx) {
|
||
return { success: false, error: 'Failed to complete transaction' };
|
||
}
|
||
|
||
// 广播交易
|
||
const result = await kavaTxService?.broadcastTx(signedTx);
|
||
return { success: true, data: result };
|
||
} catch (error) {
|
||
return { success: false, error: (error as Error).message };
|
||
}
|
||
});
|
||
|
||
// 查询交易状态
|
||
ipcMain.handle('kava:getTxStatus', async (_event, { txHash }) => {
|
||
try {
|
||
const status = await kavaTxService?.getTxStatus(txHash);
|
||
return { success: true, data: status };
|
||
} catch (error) {
|
||
return { success: false, error: (error as Error).message };
|
||
}
|
||
});
|
||
|
||
// 获取 Kava 配置
|
||
ipcMain.handle('kava:getConfig', async () => {
|
||
return kavaTxService?.getConfig();
|
||
});
|
||
|
||
// 更新 Kava 配置
|
||
ipcMain.handle('kava:updateConfig', async (_event, { config }) => {
|
||
try {
|
||
kavaTxService?.updateConfig(config);
|
||
return { success: true };
|
||
} catch (error) {
|
||
return { success: false, error: (error as Error).message };
|
||
}
|
||
});
|
||
|
||
// Kava 健康检查
|
||
ipcMain.handle('kava:healthCheck', async () => {
|
||
try {
|
||
const result = await kavaTxService?.healthCheck();
|
||
return { success: result?.ok ?? false, data: result };
|
||
} catch (error) {
|
||
return { success: false, error: (error as Error).message };
|
||
}
|
||
});
|
||
|
||
// ===========================================================================
|
||
// 对话框相关
|
||
// ===========================================================================
|
||
|
||
// 选择目录
|
||
ipcMain.handle('dialog:selectDirectory', async () => {
|
||
const result = await dialog.showOpenDialog(mainWindow!, {
|
||
properties: ['openDirectory'],
|
||
});
|
||
return result.canceled ? null : result.filePaths[0];
|
||
});
|
||
|
||
// 选择文件
|
||
ipcMain.handle('dialog:selectFile', async (_event, { filters }) => {
|
||
const result = await dialog.showOpenDialog(mainWindow!, {
|
||
properties: ['openFile'],
|
||
filters: filters || [],
|
||
});
|
||
return result.canceled ? null : result.filePaths[0];
|
||
});
|
||
|
||
// 保存文件
|
||
ipcMain.handle('dialog:saveFile', async (_event, { defaultPath, filters }) => {
|
||
const result = await dialog.showSaveDialog(mainWindow!, {
|
||
defaultPath,
|
||
filters: filters || [],
|
||
});
|
||
return result.canceled ? null : result.filePath;
|
||
});
|
||
}
|
||
|
||
// 应用生命周期
|
||
app.whenReady().then(async () => {
|
||
await initServices();
|
||
// HTTP 服务器仅在开发模式下启动
|
||
if (process.env.NODE_ENV === 'development') {
|
||
startHttpServer();
|
||
}
|
||
createWindow();
|
||
|
||
app.on('activate', () => {
|
||
if (BrowserWindow.getAllWindows().length === 0) {
|
||
createWindow();
|
||
}
|
||
});
|
||
});
|
||
|
||
app.on('window-all-closed', () => {
|
||
if (process.platform !== 'darwin') {
|
||
app.quit();
|
||
}
|
||
});
|
||
|
||
app.on('before-quit', () => {
|
||
// 清理资源
|
||
grpcClient?.disconnect();
|
||
database?.close();
|
||
httpServer?.close();
|
||
kavaTxService?.disconnect();
|
||
});
|